<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module">
    import { setRemoveNestedTemplates } from '../../lib/utils/settings.js';
    setRemoveNestedTemplates(location.search.match(/removeNestedTemplates/));
  </script>
  <script type="module" src="../../polymer-legacy.js"></script>
  <script type="module" src="./dom-repeat-elements.js"></script>
  <style>
    /* makes painting faster when testing on IOS simulator */
    x-repeat-chunked {
      display: none;
    }
  </style>
</head>
<body>

  <test-fixture id="simple">
    <template>
      <x-simple-repeat></x-simple-repeat>
    </template>
  </test-fixture>

  <test-fixture id="primitive">
    <template>
      <x-primitive-repeat></x-primitive-repeat>
    </template>
  </test-fixture>


  <test-fixture id="configured">
    <template>
      <x-nested-repeat-configured></x-nested-repeat-configured>
    </template>
  </test-fixture>

  <test-fixture id="filter-and-sort-by-nested-property">
    <template>
      <x-repeat-filter-and-sort-by-nested-property></x-repeat-filter-and-sort-by-nested-property>
    </template>
  </test-fixture>

  <test-fixture id="unconfigured">
    <template>
      <dom-bind>
        <template>
          <x-nested-repeat id="el1" items="{{items}}"></x-nested-repeat>
          <x-nested-repeat id="el2" items="{{items}}"></x-nested-repeat>
        </template>
      </dom-bind>
    </template>
  </test-fixture>

  <test-fixture id="unconfigured-mutable">
    <template>
      <dom-bind mutable-data>
        <template>
          <x-nested-repeat-mutable id="el1" items="{{items}}"></x-nested-repeat-mutable>
          <x-nested-repeat-mutable id="el2" items="{{items}}"></x-nested-repeat-mutable>
        </template>
      </dom-bind>
    </template>
  </test-fixture>

  <h4>inDocumentRepeater</h4>
  <div id="inDocumentContainerOrig">
    <dom-repeat id="inDocumentRepeater" as="itema" index-as="indexa">
      <template>
        <x-foo
               innera-prop="{{innera.prop}}"
               itema-prop="{{itema.prop}}"
               indexa="{{indexa}}">
        </x-foo>
        <template is="dom-repeat" items="{{itema.items}}" as="itemb" index-as="indexb">
          <x-foo
                 innerb-prop="{{innerb.prop}}"
                 itemb-prop="{{itemb.prop}}"
                 innera-prop="{{innera.prop}}"
                 itema-prop="{{itema.prop}}"
                 indexa="{{indexa}}"
                 indexb="{{indexb}}">
          </x-foo>
          <template is="dom-repeat" items="{{itemb.items}}" as="itemc" index-as="indexc">
            <x-foo
                   innerc-prop="{{innerc.prop}}"
                   itemc-prop="{{itemc.prop}}"
                   innerb-prop="{{innerb.prop}}"
                   itemb-prop="{{itemb.prop}}"
                   innera-prop="{{innera.prop}}"
                   itema-prop="{{itema.prop}}"
                   indexa="{{indexa}}"
                   indexb="{{indexb}}"
                   indexc="{{indexc}}">
            </x-foo>
          </template>
        </template>
      </template>
    </dom-repeat>
  </div>

  <test-fixture id="primitiveLarge">
    <template>
      <x-primitive-large></x-primitive-large>
    </template>
  </test-fixture>

  <h4>x-repeat-limit</h4>
  <x-repeat-limit id="limited"></x-repeat-limit>

  <div id="inDocumentContainer">
  </div>

  <script type="module">
import './dom-repeat-elements.js';
</script>

  <div id="nonUpgrade">
    <dom-repeat items='[{"prop": "stamped"}]'>
      <template>{{item.prop}}</template>
    </dom-repeat>
  </div>

  <script type="module">
import './dom-repeat-elements.js';
import { flush } from '../../lib/utils/flush.js';
import { calculateSplices } from '../../lib/utils/array-splice.js';
/* global limited inDocumentRepeater inDocumentContainer inDocumentContainerOrig nonUpgrade*/

/*
  Expected:

  stamped[0] ... 1
  stamped[1] ... 1-1
  stamped[2] ... 1-1-1
  stamped[3] ... 1-1-2
  stamped[4] ... 1-1-3
  stamped[5] ... 1-2
  ...
  stamped[13] .. 2
  ...
  stamped[36] .. 3-3-1
  stamped[37] .. 3-3-2
  stamped[38] .. 3-3-3
*/

function arrayDelete(el, path, item) {
  var array = el.get(path);
  var idx = array.indexOf(item);
  el.splice(path, idx, 1);
}

suite('errors', function() {

  test('items must be array', function() {
    sinon.spy(console, 'warn');
    inDocumentRepeater.items = {};
    assert.isTrue(console.warn.calledOnce, 'should warn when items is not array');
    assert.match(console.warn.firstCall.args[0], /expected array/);
    console.warn.restore();
  });

});

suite('nested pre-configured dom-repeat', function() {

  let configured;

  setup(function() {
    configured = fixture('configured');
    flush();
  });

  test('basic rendering, downward item binding', function() {
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(configured.$.repeater.renderedItemCount, 3, 'rendered item count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].computeda, 'prop-1+itemForComputedA');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[0].$.bar.itemaProp, 'prop-1');
    assert.equal(stamped[1].itembProp, 'prop-1-1');
    assert.equal(stamped[1].computedb, 'prop-1-1+itemForComputedB');
    assert.equal(stamped[1].indexa, 0);
    assert.equal(stamped[1].indexb, 0);
    assert.equal(stamped[1].$.bar.itembProp, 'prop-1-1');
    assert.equal(stamped[2].itemcProp, 'prop-1-1-1');
    assert.equal(stamped[2].computedc, 'prop-1-1-1+itemForComputedC');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].indexb, 0);
    assert.equal(stamped[2].indexc, 0);
    assert.equal(stamped[2].$.bar.itemcProp, 'prop-1-1-1');
    assert.equal(stamped[3].itemcProp, 'prop-1-1-2');
    assert.equal(stamped[3].indexa, 0);
    assert.equal(stamped[3].indexb, 0);
    assert.equal(stamped[3].indexc, 1);
    assert.equal(stamped[3].$.bar.itemcProp, 'prop-1-1-2');
    assert.equal(stamped[4].itemcProp, 'prop-1-1-3');
    assert.equal(stamped[4].indexa, 0);
    assert.equal(stamped[4].indexb, 0);
    assert.equal(stamped[4].indexc, 2);
    assert.equal(stamped[4].$.bar.itemcProp, 'prop-1-1-3');
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[13].$.bar.itemaProp, 'prop-2');
    assert.equal(stamped[36].itemcProp, 'prop-3-3-1');
    assert.equal(stamped[36].indexa, 2);
    assert.equal(stamped[36].indexb, 2);
    assert.equal(stamped[36].indexc, 0);
    assert.equal(stamped[36].$.bar.itemcProp, 'prop-3-3-1');
    assert.equal(stamped[37].itemcProp, 'prop-3-3-2');
    assert.equal(stamped[37].indexa, 2);
    assert.equal(stamped[37].indexb, 2);
    assert.equal(stamped[37].indexc, 1);
    assert.equal(stamped[37].$.bar.itemcProp, 'prop-3-3-2');
    assert.equal(stamped[38].itemcProp, 'prop-3-3-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].indexb, 2);
    assert.equal(stamped[38].indexc, 2);
    assert.equal(stamped[38].$.bar.itemcProp, 'prop-3-3-3');
  });

  test('parent scope binding', function() {
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped[0].outerProp, 'outer');
    assert.equal(stamped[0].outerItemProp, 'outerItem');
    assert.equal(stamped[1].itemaProp, 'prop-1');
    assert.equal(stamped[1].indexa, 0);
    assert.equal(stamped[1].outerProp, 'outer');
    assert.equal(stamped[1].outerItemProp, 'outerItem');
    assert.equal(stamped[2].itembProp, 'prop-1-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].indexb, 0);
    assert.equal(stamped[2].itemaProp, 'prop-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].outerProp, 'outer');
    assert.equal(stamped[2].outerItemProp, 'outerItem');
    assert.equal(stamped[38].itembProp, 'prop-3-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].indexb, 2);
    assert.equal(stamped[38].itemaProp, 'prop-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].outerProp, 'outer');
    assert.equal(stamped[38].outerItemProp, 'outerItem');
  });

  test('parent scope downward notification', function() {
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    configured.prop = 'yes';
    assert.equal(stamped[0].outerProp, 'yes');
    assert.equal(stamped[1].outerProp, 'yes');
    assert.equal(stamped[2].outerProp, 'yes');
    assert.equal(stamped[38].outerProp, 'yes');
    configured.set('item.prop', 'yay');
    assert.equal(stamped[0].outerItemProp, 'yay');
    assert.equal(stamped[1].outerItemProp, 'yay');
    assert.equal(stamped[2].outerItemProp, 'yay');
    assert.equal(stamped[38].outerItemProp, 'yay');
  });

  test('parent scope upward notification', function() {
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    stamped[38].outerProp = 'nice';
    assert.equal(configured.prop, 'nice');
    assert.equal(stamped[0].outerProp, 'nice');
    assert.equal(stamped[1].outerProp, 'nice');
    assert.equal(stamped[2].outerProp, 'nice');
    assert.equal(stamped[37].outerProp, 'nice');
    stamped[38].outerItemProp = 'cool';
    assert.equal(configured.item.prop, 'cool');
    assert.equal(stamped[0].outerItemProp, 'cool');
    assert.equal(stamped[1].outerItemProp, 'cool');
    assert.equal(stamped[2].outerItemProp, 'cool');
    assert.equal(stamped[37].outerItemProp, 'cool');
  });

  // Anonymous inner scope feature removed for time being
  // test('anonymous scope binding', function() {
  //   var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
  //   stamped[1].$.bar.inneraProp = 'changed';
  //   assert.equal(stamped[1].inneraProp, 'changed');
  //   assert.equal(stamped[2].inneraProp, 'changed');
  //   assert.equal(stamped[3].inneraProp, 'changed');
  //   assert.equal(stamped[4].inneraProp, 'changed');
  // });

  test('sort function by name on host', function() {
    // set sort fn
    configured.$.repeater.sort = 'sortDesc';
    configured.$.repeater.render();
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-3');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[26].itemaProp, 'prop-1');
    assert.equal(stamped[26].indexa, 2);
    // reset sort fn
    configured.$.repeater.sort = null;
    configured.$.repeater.render();
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[26].itemaProp, 'prop-3');
    assert.equal(stamped[26].indexa, 2);
  });

  test('sort function imperative', function() {
    // set sort fn
    configured.$.repeater.sort = function(a, b) {
      return b.prop.localeCompare(a.prop);
    };
    configured.$.repeater.render();
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-3');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[26].itemaProp, 'prop-1');
    assert.equal(stamped[26].indexa, 2);
    // reset sort fn
    configured.$.repeater.sort = null;
    configured.$.repeater.render();
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[26].itemaProp, 'prop-3');
    assert.equal(stamped[26].indexa, 2);
  });

  test('filter function by name on host', function() {
    // set filter fn
    configured.$.repeater.filter = 'filter2nd';
    configured.$.repeater.render();
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
    assert.equal(configured.$.repeater.renderedItemCount, 2, 'rendered item count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-3');
    assert.equal(stamped[13].indexa, 1);
    // reset filter fn
    configured.$.repeater.filter = null;
    configured.$.repeater.render();
    stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[26].itemaProp, 'prop-3');
    assert.equal(stamped[26].indexa, 2);
  });

  test('filter function imperative', function() {
    // set filter fn
    configured.$.repeater.filter = function(o) {
      return o.prop.indexOf('2') < 0;
    };
    configured.$.repeater.render();
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-3');
    assert.equal(stamped[13].indexa, 1);
    // reset filter fn
    configured.$.repeater.filter = null;
    configured.$.repeater.render();
    stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[26].itemaProp, 'prop-3');
    assert.equal(stamped[26].indexa, 2);
  });

  test('sort and filter function by name on host', function() {
    // set filter fn
    configured.$.repeater.sort = 'sortDesc';
    configured.$.repeater.filter = 'filter2nd';
    configured.$.repeater.render();
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-3');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-1');
    assert.equal(stamped[13].indexa, 1);
    // reset filter fn
    configured.$.repeater.sort = null;
    configured.$.repeater.filter = null;
    configured.$.repeater.render();
    stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[26].itemaProp, 'prop-3');
    assert.equal(stamped[26].indexa, 2);
  });

  test('observe refreshes sort and filter', function(done) {
    // set filter fn
    configured.$.repeater.sort = 'sortDesc';
    configured.$.repeater.filter = 'filter2nd';
    configured.$.repeater.render();
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-3');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-1');
    assert.equal(stamped[13].indexa, 1);

    // Update observed prop to be in filter
    configured.set(['items', 1, 'prop'], 'prop-0');
    // avoid imperative/synchronous refresh() since that forces a full refresh
    setTimeout(function() {
      stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped[0].itemaProp, 'prop-3');
      assert.equal(stamped[0].indexa, 0);
      assert.equal(stamped[13].itemaProp, 'prop-1');
      assert.equal(stamped[13].indexa, 1);
      assert.equal(stamped[26].itemaProp, 'prop-0');
      assert.equal(stamped[26].indexa, 2);

      // Update observed prop back to be out of the filter
      configured.set(['items', 1, 'prop'], 'prop-2');
      setTimeout(function() {
        var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
        assert.equal(stamped[0].itemaProp, 'prop-3');
        assert.equal(stamped[0].indexa, 0);
        assert.equal(stamped[13].itemaProp, 'prop-1');
        assert.equal(stamped[13].indexa, 1);

        // reset filter fn
        configured.$.repeater.sort = null;
        configured.$.repeater.filter = null;
        configured.$.repeater.render();
        stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
        assert.equal(stamped[0].itemaProp, 'prop-1');
        assert.equal(stamped[0].indexa, 0);
        assert.equal(stamped[13].itemaProp, 'prop-2');
        assert.equal(stamped[13].indexa, 1);
        assert.equal(stamped[26].itemaProp, 'prop-3');
        assert.equal(stamped[26].indexa, 2);

        done();
      });
    });
  });

  test('item change refreshes sort and filter', function(done) {
    // set filter fn
    configured.$.repeater.sort = 'sortDesc';
    configured.$.repeater.filter = 'filter2nd';
    configured.$.repeater.render();
    var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-3');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[13].itemaProp, 'prop-1');
    assert.equal(stamped[13].indexa, 1);

    // Update observed prop to be in filter (set new object)
    configured.set(['items', 1], Object.assign({}, configured.items[1], {prop: 'prop-0'}));
    // avoid imperative/synchronous refresh() since that forces a full refresh
    setTimeout(function() {
      stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped[0].itemaProp, 'prop-3');
      assert.equal(stamped[0].indexa, 0);
      assert.equal(stamped[13].itemaProp, 'prop-1');
      assert.equal(stamped[13].indexa, 1);
      assert.equal(stamped[26].itemaProp, 'prop-0');
      assert.equal(stamped[26].indexa, 2);

      // Update observed prop back to be out of the filter (set new object)
      configured.set(['items', 1], Object.assign({}, configured.items[1], {prop: 'prop-2'}));
      setTimeout(function() {
        var stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
        assert.equal(stamped[0].itemaProp, 'prop-3');
        assert.equal(stamped[0].indexa, 0);
        assert.equal(stamped[13].itemaProp, 'prop-1');
        assert.equal(stamped[13].indexa, 1);

        // reset filter fn
        configured.$.repeater.sort = null;
        configured.$.repeater.filter = null;
        configured.$.repeater.render();
        stamped = configured.root.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
        assert.equal(stamped[0].itemaProp, 'prop-1');
        assert.equal(stamped[0].indexa, 0);
        assert.equal(stamped[13].itemaProp, 'prop-2');
        assert.equal(stamped[13].indexa, 1);
        assert.equal(stamped[26].itemaProp, 'prop-3');
        assert.equal(stamped[26].indexa, 2);

        done();
      });
    });
  });
});

suite('filter and sort by nested property', function () {
  let fixtureInstance;

  setup(function() {
    fixtureInstance = fixture('filter-and-sort-by-nested-property');
    flush();
  });

  test('change of item nested-property refreshes sort and filter', function(done) {
    // Original values for `item.prop.nestedProp` are numbers 0 through 4
    const repeater = fixtureInstance.$.repeater;
    repeater.sort = (a, b) => b.prop.nestedProp - a.prop.nestedProp; // Desc order
    repeater.filter = (a) => a.prop.nestedProp >= 1; // => Just 1 through 4
    repeater.observe = 'prop.nestedProp';
    repeater.render();

    const stamped = fixtureInstance.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 4, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 4);
    assert.equal(stamped[1].itemaProp, 3);
    assert.equal(stamped[2].itemaProp, 2);
    assert.equal(stamped[3].itemaProp, 1);

    // Update observed nested prop
    for (let i = 0; i < 5; i++) {
      const x = fixtureInstance.get(['items', i, 'prop', 'nestedProp']);
      fixtureInstance.set(['items', i, 'prop', 'nestedProp'], (x + 10) * 2);
    }

    // Wait for changes to be reflected to the DOM
    setTimeout(function() {
      const stamped = fixtureInstance.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 5, 'total stamped count incorrect');
      assert.equal(stamped[0].itemaProp, 28);
      assert.equal(stamped[1].itemaProp, 26);
      assert.equal(stamped[2].itemaProp, 24);
      assert.equal(stamped[3].itemaProp, 22);
      assert.equal(stamped[4].itemaProp, 20);

      done();
    });
  });
});

suite('nested un-configured dom-repeat in document', function() {

  test('basic rendering, downward item binding', function() {
    inDocumentRepeater.items = window.getData();
    inDocumentRepeater.render();
    var stamped = inDocumentContainerOrig.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[0].$.bar.itemaProp, 'prop-1');
    assert.equal(stamped[1].itembProp, 'prop-1-1');
    assert.equal(stamped[1].indexa, 0);
    assert.equal(stamped[1].indexb, 0);
    assert.equal(stamped[1].$.bar.itembProp, 'prop-1-1');
    assert.equal(stamped[2].itemcProp, 'prop-1-1-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].indexb, 0);
    assert.equal(stamped[2].indexc, 0);
    assert.equal(stamped[2].$.bar.itemcProp, 'prop-1-1-1');
    assert.equal(stamped[3].itemcProp, 'prop-1-1-2');
    assert.equal(stamped[3].indexa, 0);
    assert.equal(stamped[3].indexb, 0);
    assert.equal(stamped[3].indexc, 1);
    assert.equal(stamped[3].$.bar.itemcProp, 'prop-1-1-2');
    assert.equal(stamped[4].itemcProp, 'prop-1-1-3');
    assert.equal(stamped[4].indexa, 0);
    assert.equal(stamped[4].indexb, 0);
    assert.equal(stamped[4].indexc, 2);
    assert.equal(stamped[4].$.bar.itemcProp, 'prop-1-1-3');
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[13].$.bar.itemaProp, 'prop-2');
    assert.equal(stamped[36].itemcProp, 'prop-3-3-1');
    assert.equal(stamped[36].indexa, 2);
    assert.equal(stamped[36].indexb, 2);
    assert.equal(stamped[36].indexc, 0);
    assert.equal(stamped[36].$.bar.itemcProp, 'prop-3-3-1');
    assert.equal(stamped[37].itemcProp, 'prop-3-3-2');
    assert.equal(stamped[37].indexa, 2);
    assert.equal(stamped[37].indexb, 2);
    assert.equal(stamped[37].indexc, 1);
    assert.equal(stamped[37].$.bar.itemcProp, 'prop-3-3-2');
    assert.equal(stamped[38].itemcProp, 'prop-3-3-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].indexb, 2);
    assert.equal(stamped[38].indexc, 2);
    assert.equal(stamped[38].$.bar.itemcProp, 'prop-3-3-3');
  });

  test('move to different container', function() {
    var repeater = inDocumentRepeater;
    inDocumentContainer.appendChild(repeater);
    var stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[0].$.bar.itemaProp, 'prop-1');
    assert.equal(stamped[1].itembProp, 'prop-1-1');
    assert.equal(stamped[1].indexa, 0);
    assert.equal(stamped[1].indexb, 0);
    assert.equal(stamped[1].$.bar.itembProp, 'prop-1-1');
    assert.equal(stamped[2].itemcProp, 'prop-1-1-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].indexb, 0);
    assert.equal(stamped[2].indexc, 0);
    assert.equal(stamped[2].$.bar.itemcProp, 'prop-1-1-1');
    assert.equal(stamped[3].itemcProp, 'prop-1-1-2');
    assert.equal(stamped[3].indexa, 0);
    assert.equal(stamped[3].indexb, 0);
    assert.equal(stamped[3].indexc, 1);
    assert.equal(stamped[3].$.bar.itemcProp, 'prop-1-1-2');
    assert.equal(stamped[4].itemcProp, 'prop-1-1-3');
    assert.equal(stamped[4].indexa, 0);
    assert.equal(stamped[4].indexb, 0);
    assert.equal(stamped[4].indexc, 2);
    assert.equal(stamped[4].$.bar.itemcProp, 'prop-1-1-3');
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[13].$.bar.itemaProp, 'prop-2');
    assert.equal(stamped[36].itemcProp, 'prop-3-3-1');
    assert.equal(stamped[36].indexa, 2);
    assert.equal(stamped[36].indexb, 2);
    assert.equal(stamped[36].indexc, 0);
    assert.equal(stamped[36].$.bar.itemcProp, 'prop-3-3-1');
    assert.equal(stamped[37].itemcProp, 'prop-3-3-2');
    assert.equal(stamped[37].indexa, 2);
    assert.equal(stamped[37].indexb, 2);
    assert.equal(stamped[37].indexc, 1);
    assert.equal(stamped[37].$.bar.itemcProp, 'prop-3-3-2');
    assert.equal(stamped[38].itemcProp, 'prop-3-3-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].indexb, 2);
    assert.equal(stamped[38].indexc, 2);
    assert.equal(stamped[38].$.bar.itemcProp, 'prop-3-3-3');
  });

  test('basic rendering, downward item binding', function() {
    var r = inDocumentRepeater;
    r.parentElement.removeChild(r);
    inDocumentContainer.appendChild(r);

    var stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[0].$.bar.itemaProp, 'prop-1');
    assert.equal(stamped[1].itembProp, 'prop-1-1');
    assert.equal(stamped[1].indexa, 0);
    assert.equal(stamped[1].indexb, 0);
    assert.equal(stamped[1].$.bar.itembProp, 'prop-1-1');
    assert.equal(stamped[2].itemcProp, 'prop-1-1-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].indexb, 0);
    assert.equal(stamped[2].indexc, 0);
    assert.equal(stamped[2].$.bar.itemcProp, 'prop-1-1-1');
    assert.equal(stamped[3].itemcProp, 'prop-1-1-2');
    assert.equal(stamped[3].indexa, 0);
    assert.equal(stamped[3].indexb, 0);
    assert.equal(stamped[3].indexc, 1);
    assert.equal(stamped[3].$.bar.itemcProp, 'prop-1-1-2');
    assert.equal(stamped[4].itemcProp, 'prop-1-1-3');
    assert.equal(stamped[4].indexa, 0);
    assert.equal(stamped[4].indexb, 0);
    assert.equal(stamped[4].indexc, 2);
    assert.equal(stamped[4].$.bar.itemcProp, 'prop-1-1-3');
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[13].$.bar.itemaProp, 'prop-2');
    assert.equal(stamped[36].itemcProp, 'prop-3-3-1');
    assert.equal(stamped[36].indexa, 2);
    assert.equal(stamped[36].indexb, 2);
    assert.equal(stamped[36].indexc, 0);
    assert.equal(stamped[36].$.bar.itemcProp, 'prop-3-3-1');
    assert.equal(stamped[37].itemcProp, 'prop-3-3-2');
    assert.equal(stamped[37].indexa, 2);
    assert.equal(stamped[37].indexb, 2);
    assert.equal(stamped[37].indexc, 1);
    assert.equal(stamped[37].$.bar.itemcProp, 'prop-3-3-2');
    assert.equal(stamped[38].itemcProp, 'prop-3-3-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].indexb, 2);
    assert.equal(stamped[38].indexc, 2);
    assert.equal(stamped[38].$.bar.itemcProp, 'prop-3-3-3');
  });

  test('parent scope binding', function() {
    var stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped[1].itemaProp, 'prop-1');
    assert.equal(stamped[1].indexa, 0);
    assert.equal(stamped[2].itembProp, 'prop-1-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].indexb, 0);
    assert.equal(stamped[2].itemaProp, 'prop-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[38].itembProp, 'prop-3-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].indexb, 2);
    assert.equal(stamped[38].itemaProp, 'prop-3');
    assert.equal(stamped[38].indexa, 2);
  });

  // Anonymous inner scope feature removed for time being
  // test('anonymous scope binding', function() {
  //   var stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-repeat)');
  //   stamped[1].$.bar.inneraProp = 'changed';
  //   assert.equal(stamped[1].inneraProp, 'changed');
  //   assert.equal(stamped[2].inneraProp, 'changed');
  //   assert.equal(stamped[3].inneraProp, 'changed');
  //   assert.equal(stamped[4].inneraProp, 'changed');
  // });

});

suite('nested un-configured dom-repeat', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    unconfigured.$.el1.prop = 'outer';
    unconfigured.$.el1.item = {prop: 'outerItem'};
    flush();
  });

  test('basic rendering, downward item binding', function() {
    var stamped = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert.equal(stamped[0].indexa, 0);
    assert.equal(stamped[0].$.bar.itemaProp, 'prop-1');
    assert.equal(stamped[1].itembProp, 'prop-1-1');
    assert.equal(stamped[1].indexa, 0);
    assert.equal(stamped[1].indexb, 0);
    assert.equal(stamped[1].$.bar.itembProp, 'prop-1-1');
    assert.equal(stamped[2].itemcProp, 'prop-1-1-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].indexb, 0);
    assert.equal(stamped[2].indexc, 0);
    assert.equal(stamped[2].$.bar.itemcProp, 'prop-1-1-1');
    assert.equal(stamped[3].itemcProp, 'prop-1-1-2');
    assert.equal(stamped[3].indexa, 0);
    assert.equal(stamped[3].indexb, 0);
    assert.equal(stamped[3].indexc, 1);
    assert.equal(stamped[3].$.bar.itemcProp, 'prop-1-1-2');
    assert.equal(stamped[4].itemcProp, 'prop-1-1-3');
    assert.equal(stamped[4].indexa, 0);
    assert.equal(stamped[4].indexb, 0);
    assert.equal(stamped[4].indexc, 2);
    assert.equal(stamped[4].$.bar.itemcProp, 'prop-1-1-3');
    assert.equal(stamped[13].itemaProp, 'prop-2');
    assert.equal(stamped[13].indexa, 1);
    assert.equal(stamped[13].$.bar.itemaProp, 'prop-2');
    assert.equal(stamped[36].itemcProp, 'prop-3-3-1');
    assert.equal(stamped[36].indexa, 2);
    assert.equal(stamped[36].indexb, 2);
    assert.equal(stamped[36].indexc, 0);
    assert.equal(stamped[36].$.bar.itemcProp, 'prop-3-3-1');
    assert.equal(stamped[37].itemcProp, 'prop-3-3-2');
    assert.equal(stamped[37].indexa, 2);
    assert.equal(stamped[37].indexb, 2);
    assert.equal(stamped[37].indexc, 1);
    assert.equal(stamped[37].$.bar.itemcProp, 'prop-3-3-2');
    assert.equal(stamped[38].itemcProp, 'prop-3-3-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].indexb, 2);
    assert.equal(stamped[38].indexc, 2);
    assert.equal(stamped[38].$.bar.itemcProp, 'prop-3-3-3');
  });

  test('parent scope binding', function() {
    var stamped = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped[0].outerProp, 'outer');
    assert.equal(stamped[0].outerItemProp, 'outerItem');
    assert.equal(stamped[1].itemaProp, 'prop-1');
    assert.equal(stamped[1].indexa, 0);
    assert.equal(stamped[1].outerProp, 'outer');
    assert.equal(stamped[1].outerItemProp, 'outerItem');
    assert.equal(stamped[2].itembProp, 'prop-1-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].indexb, 0);
    assert.equal(stamped[2].itemaProp, 'prop-1');
    assert.equal(stamped[2].indexa, 0);
    assert.equal(stamped[2].outerProp, 'outer');
    assert.equal(stamped[2].outerItemProp, 'outerItem');
    assert.equal(stamped[38].itembProp, 'prop-3-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].indexb, 2);
    assert.equal(stamped[38].itemaProp, 'prop-3');
    assert.equal(stamped[38].indexa, 2);
    assert.equal(stamped[38].outerProp, 'outer');
    assert.equal(stamped[38].outerItemProp, 'outerItem');
  });

  test('parent scope downward notification', function() {
    var stamped = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    unconfigured.$.el1.prop = 'yes';
    assert.equal(stamped[0].outerProp, 'yes');
    assert.equal(stamped[1].outerProp, 'yes');
    assert.equal(stamped[2].outerProp, 'yes');
    assert.equal(stamped[38].outerProp, 'yes');
    unconfigured.$.el1.set('item.prop', 'yay');
    assert.equal(stamped[0].outerItemProp, 'yay');
    assert.equal(stamped[1].outerItemProp, 'yay');
    assert.equal(stamped[2].outerItemProp, 'yay');
    assert.equal(stamped[38].outerItemProp, 'yay');
  });

  test('parent scope upward notification', function() {
    var stamped = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    stamped[38].outerProp = 'nice';
    assert.equal(unconfigured.$.el1.prop, 'nice');
    assert.equal(stamped[0].outerProp, 'nice');
    assert.equal(stamped[1].outerProp, 'nice');
    assert.equal(stamped[2].outerProp, 'nice');
    assert.equal(stamped[37].outerProp, 'nice');
    stamped[38].outerItemProp = 'cool';
    assert.equal(unconfigured.$.el1.item.prop, 'cool');
    assert.equal(stamped[0].outerItemProp, 'cool');
    assert.equal(stamped[1].outerItemProp, 'cool');
    assert.equal(stamped[2].outerItemProp, 'cool');
    assert.equal(stamped[37].outerItemProp, 'cool');
  });

  // Anonymous inner scope feature removed for time being
  // test('anonymous scope binding', function() {
  //   var stamped = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
  //   stamped[1].$.bar.inneraProp = 'changed';
  //   assert.equal(stamped[1].inneraProp, 'changed');
  //   assert.equal(stamped[2].inneraProp, 'changed');
  //   assert.equal(stamped[3].inneraProp, 'changed');
  //   assert.equal(stamped[4].inneraProp, 'changed');
  // });

  test('event handlers', function() {
    var stamped = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    stamped[0].fire('test1');
    assert.equal(unconfigured.$.el1.testHandler1Count, 1);
    stamped[1].fire('test2');
    assert.equal(unconfigured.$.el1.testHandler2Count, 1);
    stamped[2].fire('test3');
    assert.equal(unconfigured.$.el1.testHandler3Count, 1);
  });

});

suite('array notification between two dom-repeats', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    flush();
  });

  test('change to item from one dom-repeat to other', function() {
    var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    var stamped2 = unconfigured.$.el2.root.querySelectorAll('*:not(template):not(dom-repeat)');

    assert.equal(stamped1[0].itemaProp, 'prop-1');
    assert.equal(stamped1[0].indexa, 0);
    assert.equal(stamped2[0].itemaProp, 'prop-1');
    assert.equal(stamped2[0].indexa, 0);
    stamped1[0].$.bar.itemaProp = 'changed';
    assert.equal(stamped2[0].itemaProp, 'changed');
    stamped2[0].$.bar.itemaProp = 'prop-1';
    assert.equal(stamped1[0].itemaProp, 'prop-1');
    assert.equal(stamped1[0].indexa, 0);

    assert.equal(stamped1[1].itembProp, 'prop-1-1');
    assert.equal(stamped1[1].indexa, 0);
    assert.equal(stamped1[1].indexb, 0);
    assert.equal(stamped2[1].itembProp, 'prop-1-1');
    assert.equal(stamped2[1].indexa, 0);
    assert.equal(stamped2[1].indexb, 0);
    stamped1[1].$.bar.itembProp = 'changed';
    assert.equal(stamped2[1].itembProp, 'changed');
    stamped2[1].$.bar.itembProp = 'prop-1-1';
    assert.equal(stamped1[1].itembProp, 'prop-1-1');
    assert.equal(stamped1[1].indexa, 0);
    assert.equal(stamped1[1].indexb, 0);

    assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
    assert.equal(stamped1[2].indexa, 0);
    assert.equal(stamped1[2].indexb, 0);
    assert.equal(stamped1[2].indexc, 0);
    assert.equal(stamped2[2].itemcProp, 'prop-1-1-1');
    assert.equal(stamped2[2].indexa, 0);
    assert.equal(stamped2[2].indexb, 0);
    assert.equal(stamped2[2].indexc, 0);
    stamped1[2].$.bar.itemcProp = 'changed';
    assert.equal(stamped2[2].itemcProp, 'changed');
    stamped2[2].$.bar.itemcProp = 'prop-1-1-1';
    assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
    assert.equal(stamped1[2].indexa, 0);
    assert.equal(stamped1[2].indexb, 0);
    assert.equal(stamped1[2].indexc, 0);

    assert.equal(stamped1[38].itemcProp, 'prop-3-3-3');
    assert.equal(stamped1[38].indexa, 2);
    assert.equal(stamped1[38].indexb, 2);
    assert.equal(stamped1[38].indexc, 2);
    assert.equal(stamped2[38].itemcProp, 'prop-3-3-3');
    assert.equal(stamped2[38].indexa, 2);
    assert.equal(stamped2[38].indexb, 2);
    assert.equal(stamped2[38].indexc, 2);
    stamped1[38].$.bar.itemcProp = 'changed';
    assert.equal(stamped2[38].itemcProp, 'changed');
    stamped2[38].$.bar.itemcProp = 'prop-3-3-3';
    assert.equal(stamped1[38].itemcProp, 'prop-3-3-3');
    assert.equal(stamped1[38].indexa, 2);
    assert.equal(stamped1[38].indexb, 2);
    assert.equal(stamped1[38].indexc, 2);
  });

  test('change to non-item scope doesn\'t affect other dom-repeat', function() {
    var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    var stamped2 = unconfigured.$.el2.root.querySelectorAll('*:not(template):not(dom-repeat)');

    unconfigured.$.el1.prop = 'foo';
    unconfigured.$.el2.prop = 'bar';
    assert.equal(stamped1[0].outerProp, 'foo');
    assert.equal(stamped1[1].outerProp, 'foo');
    assert.equal(stamped1[2].outerProp, 'foo');
    assert.equal(stamped2[0].outerProp, 'bar');
    assert.equal(stamped2[1].outerProp, 'bar');
    assert.equal(stamped2[2].outerProp, 'bar');

    stamped1[1].$.bar.inneraProp = 'bar';
  });

});

suite('reacting to changes, array sort', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    flush();
    unconfigured.$.el1.domUpdateHandlerCount = 0;
  });

  test('push', function(done) {
    unconfigured.push('items', {prop: 'new-1'}, {prop: 'new-2'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[38].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[38].indexa, 2);
      assert.equal(stamped1[38].indexb, 2);
      assert.equal(stamped1[38].indexc, 2);
      assert.equal(stamped1[39].itemaProp, 'new-1');
      assert.equal(stamped1[40].itemaProp, 'new-2');
      assert.equal(stamped1[41], undefined);
      assert.equal(stamped2[38].itemcProp, 'prop-3-3-3');
      assert.equal(stamped2[38].indexa, 2);
      assert.equal(stamped2[38].indexb, 2);
      assert.equal(stamped2[38].indexc, 2);
      assert.equal(stamped2[39].itemaProp, 'new-1');
      assert.equal(stamped2[40].itemaProp, 'new-2');
      assert.equal(stamped2[41], undefined);
      assert.equal(unconfigured.$.el1.domUpdateHandlerCount, 1);
      done();
    });
  });

  test('pop', function(done) {
    unconfigured.pop('items');
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[25].itemcProp, 'prop-2-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      assert.equal(stamped2[25].itemcProp, 'prop-2-3-3');
      assert.equal(stamped2[25].indexa, 1);
      assert.equal(stamped2[25].indexb, 2);
      assert.equal(stamped2[25].indexc, 2);
      assert.equal(stamped2[26], undefined);
      done();
    });
  });

  test('unshift', function(done) {
    unconfigured.unshift('items', {prop: 'new-1'}, {prop: 'new-2'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'new-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'new-2');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itemaProp, 'prop-1');
      assert.equal(stamped1[2].indexa, 2);
      assert.equal(stamped2[0].itemaProp, 'new-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped2[1].itemaProp, 'new-2');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped2[2].itemaProp, 'prop-1');
      assert.equal(stamped2[2].indexa, 2);
      done();
    });
  });

  test('shift', function(done) {
    unconfigured.shift('items');
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-2');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-2-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-2-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped2[0].itemaProp, 'prop-2');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-2-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-2-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      done();
    });
  });

  test('splice - remove from middle', function(done) {
    unconfigured.splice('items', 1, 1)[0];
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'prop-3');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itembProp, 'prop-3-1');
      assert.equal(stamped1[14].indexa, 1);
      assert.equal(stamped1[14].indexb, 0);
      assert.equal(stamped1[15].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[15].indexa, 1);
      assert.equal(stamped1[15].indexb, 0);
      assert.equal(stamped1[15].indexc, 0);
      assert.equal(stamped2[0].itemaProp, 'prop-1');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-1-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      assert.equal(stamped2[13].itemaProp, 'prop-3');
      assert.equal(stamped2[13].indexa, 1);
      assert.equal(stamped2[14].itembProp, 'prop-3-1');
      assert.equal(stamped2[14].indexa, 1);
      assert.equal(stamped2[14].indexb, 0);
      assert.equal(stamped2[15].itemcProp, 'prop-3-1-1');
      assert.equal(stamped2[15].indexa, 1);
      assert.equal(stamped2[15].indexb, 0);
      assert.equal(stamped2[15].indexc, 0);
      done();
    });
  });

  test('splice - add to middle', function(done) {
    unconfigured.splice('items', 1, 0, {prop: 'new-1'}, {prop: 'new-2'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'new-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itemaProp, 'new-2');
      assert.equal(stamped1[14].indexa, 2);
      assert.equal(stamped1[15].itemaProp, 'prop-2');
      assert.equal(stamped1[15].indexa, 3);
      assert.equal(stamped1[16].itembProp, 'prop-2-1');
      assert.equal(stamped1[16].indexa, 3);
      assert.equal(stamped1[16].indexb, 0);
      assert.equal(stamped1[17].itemcProp, 'prop-2-1-1');
      assert.equal(stamped1[17].indexa, 3);
      assert.equal(stamped1[17].indexb, 0);
      assert.equal(stamped1[17].indexc, 0);
      assert.equal(stamped2[0].itemaProp, 'prop-1');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-1-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      assert.equal(stamped2[13].itemaProp, 'new-1');
      assert.equal(stamped2[13].indexa, 1);
      assert.equal(stamped2[14].itemaProp, 'new-2');
      assert.equal(stamped2[14].indexa, 2);
      assert.equal(stamped2[15].itemaProp, 'prop-2');
      assert.equal(stamped2[15].indexa, 3);
      assert.equal(stamped2[16].itembProp, 'prop-2-1');
      assert.equal(stamped2[16].indexa, 3);
      assert.equal(stamped2[16].indexb, 0);
      assert.equal(stamped2[17].itemcProp, 'prop-2-1-1');
      assert.equal(stamped2[17].indexa, 3);
      assert.equal(stamped2[17].indexb, 0);
      assert.equal(stamped2[17].indexc, 0);
      done();
    });
  });

  test('splice - replace in middle', function(done) {
    unconfigured.splice('items', 1, 2, {prop: 'new-1'}, {prop: 'new-2'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'new-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itemaProp, 'new-2');
      assert.equal(stamped1[14].indexa, 2);
      assert.equal(stamped1[15], undefined);
      assert.equal(stamped2[0].itemaProp, 'prop-1');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-1-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      assert.equal(stamped2[13].itemaProp, 'new-1');
      assert.equal(stamped2[13].indexa, 1);
      assert.equal(stamped2[14].itemaProp, 'new-2');
      assert.equal(stamped2[14].indexa, 2);
      assert.equal(stamped2[15], undefined);
      done();
    });
  });

});

suite('reacting to changes, array sort with filter', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    unconfigured.$.el1.$.repeater.filter = function(o) {
      return o.prop.indexOf('2') < 0;
    };
    flush();
  });

  test('push', function(done) {
    unconfigured.push('items', {prop: 'new-1'}, {prop: 'new-2'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[23].itemcProp, 'prop-3-3-1');
      assert.equal(stamped1[23].indexa, 1);
      assert.equal(stamped1[23].indexb, 2);
      assert.equal(stamped1[23].indexc, 0);
      assert.equal(stamped1[24].itemcProp, 'prop-3-3-2');
      assert.equal(stamped1[24].indexa, 1);
      assert.equal(stamped1[24].indexb, 2);
      assert.equal(stamped1[24].indexc, 1);
      assert.equal(stamped1[25].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26].itemaProp, 'new-1');
      assert.equal(stamped1[26].indexa, 2);
      assert.equal(stamped1[27], undefined);
      done();
    });
  });

  test('pop', function(done) {
    unconfigured.pop('items');
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[10].itemcProp, 'prop-1-3-1');
      assert.equal(stamped1[10].indexa, 0);
      assert.equal(stamped1[10].indexb, 2);
      assert.equal(stamped1[10].indexc, 0);
      assert.equal(stamped1[11].itemcProp, 'prop-1-3-2');
      assert.equal(stamped1[11].indexa, 0);
      assert.equal(stamped1[11].indexb, 2);
      assert.equal(stamped1[11].indexc, 1);
      assert.equal(stamped1[12].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[12].indexa, 0);
      assert.equal(stamped1[12].indexb, 2);
      assert.equal(stamped1[12].indexc, 2);
      assert.equal(stamped1[13], undefined);
      done();
    });
  });

  test('unshift', function(done) {
    unconfigured.unshift('items', {prop: 'new-1'}, {prop: 'new-2'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'new-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-1');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-1-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      done();
    });
  });

  test('shift', function(done) {
    unconfigured.shift('items');
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[10].itemcProp, 'prop-3-3-1');
      assert.equal(stamped1[10].indexa, 0);
      assert.equal(stamped1[10].indexb, 2);
      assert.equal(stamped1[10].indexc, 0);
      assert.equal(stamped1[11].itemcProp, 'prop-3-3-2');
      assert.equal(stamped1[11].indexa, 0);
      assert.equal(stamped1[11].indexb, 2);
      assert.equal(stamped1[11].indexc, 1);
      assert.equal(stamped1[12].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[12].indexa, 0);
      assert.equal(stamped1[12].indexb, 2);
      assert.equal(stamped1[12].indexc, 2);
      assert.equal(stamped1[13], undefined);
      done();
    });
  });

  test('splice - remove from middle', function(done) {
    unconfigured.splice('items', 1, 1)[0];
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[23].itemcProp, 'prop-3-3-1');
      assert.equal(stamped1[23].indexa, 1);
      assert.equal(stamped1[23].indexb, 2);
      assert.equal(stamped1[23].indexc, 0);
      assert.equal(stamped1[24].itemcProp, 'prop-3-3-2');
      assert.equal(stamped1[24].indexa, 1);
      assert.equal(stamped1[24].indexb, 2);
      assert.equal(stamped1[24].indexc, 1);
      assert.equal(stamped1[25].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      done();
    });
  });

  test('splice - add to middle', function(done) {
    unconfigured.splice('items', 1, 0, {prop: 'new-1'}, {prop: 'new-2'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'new-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itemaProp, 'prop-3');
      assert.equal(stamped1[14].indexa, 2);
      assert.equal(stamped1[15].itembProp, 'prop-3-1');
      assert.equal(stamped1[15].indexa, 2);
      assert.equal(stamped1[15].indexb, 0);
      assert.equal(stamped1[16].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[16].indexa, 2);
      assert.equal(stamped1[16].indexb, 0);
      assert.equal(stamped1[16].indexc, 0);
      done();
    });
  });

  test('splice - replace in middle', function(done) {
    unconfigured.splice('items', 1, 2, {prop: 'new-1'}, {prop: 'new-2'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'new-1');
      assert.equal(stamped1[14], undefined);
      done();
    });
  });

});

suite('reacting to changes, view sort', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    unconfigured.$.el1.$.repeater.sort = function(a, b) {
      return b.prop == a.prop ? 0 : b.prop < a.prop ? -1 : 1;
    };
    flush();
  });

  test('push', function(done) {
    unconfigured.push('items', {prop: 'a'}, {prop: 'prop-1*'}, {prop: 'z'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[27].itemaProp, 'prop-1*');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[40].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[40].indexa, 4);
      assert.equal(stamped1[40].indexb, 2);
      assert.equal(stamped1[40].indexc, 2);
      assert.equal(stamped1[41].itemaProp, 'a');
      assert.equal(stamped1[41].indexa, 5);
      done();
    });
  });

  test('pop', function(done) {
    unconfigured.pop('items');
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[25].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      done();
    });
  });

  test('unshift', function(done) {
    unconfigured.unshift('items', {prop: 'a'}, {prop: 'prop-1*'}, {prop: 'z'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[27].itemaProp, 'prop-1*');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[40].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[40].indexa, 4);
      assert.equal(stamped1[40].indexb, 2);
      assert.equal(stamped1[40].indexc, 2);
      assert.equal(stamped1[41].itemaProp, 'a');
      assert.equal(stamped1[41].indexa, 5);
      done();
    });
  });

  test('shift', function(done) {
    unconfigured.shift('items');
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[23].itemcProp, 'prop-2-3-1');
      assert.equal(stamped1[23].indexa, 1);
      assert.equal(stamped1[23].indexb, 2);
      assert.equal(stamped1[23].indexc, 0);
      assert.equal(stamped1[24].itemcProp, 'prop-2-3-2');
      assert.equal(stamped1[24].indexa, 1);
      assert.equal(stamped1[24].indexb, 2);
      assert.equal(stamped1[24].indexc, 1);
      assert.equal(stamped1[25].itemcProp, 'prop-2-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      done();
    });
  });

  test('splice - remove from middle', function(done) {
    unconfigured.splice('items', 1, 1)[0];
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'prop-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itembProp, 'prop-1-1');
      assert.equal(stamped1[14].indexa, 1);
      assert.equal(stamped1[14].indexb, 0);
      assert.equal(stamped1[15].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[15].indexa, 1);
      assert.equal(stamped1[15].indexb, 0);
      assert.equal(stamped1[15].indexc, 0);
      assert.equal(stamped1[23].itemcProp, 'prop-1-3-1');
      assert.equal(stamped1[23].indexa, 1);
      assert.equal(stamped1[23].indexb, 2);
      assert.equal(stamped1[23].indexc, 0);
      assert.equal(stamped1[24].itemcProp, 'prop-1-3-2');
      assert.equal(stamped1[24].indexa, 1);
      assert.equal(stamped1[24].indexb, 2);
      assert.equal(stamped1[24].indexc, 1);
      assert.equal(stamped1[25].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      done();
    });
  });

  test('splice - add to middle', function(done) {
    unconfigured.splice('items', 1, 0, {prop: 'a'}, {prop: 'prop-1*'}, {prop: 'z'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[27].itemaProp, 'prop-1*');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[40].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[40].indexa, 4);
      assert.equal(stamped1[40].indexb, 2);
      assert.equal(stamped1[40].indexc, 2);
      assert.equal(stamped1[41].itemaProp, 'a');
      assert.equal(stamped1[41].indexa, 5);
      done();
    });
  });

  test('splice - replace in middle', function(done) {
    unconfigured.splice('items', 1, 2, {prop: 'a'}, {prop: 'prop-1*'}, {prop: 'z'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-1*');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itemaProp, 'prop-1');
      assert.equal(stamped1[2].indexa, 2);
      assert.equal(stamped1[3].itembProp, 'prop-1-1');
      assert.equal(stamped1[3].indexa, 2);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[4].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[4].indexa, 2);
      assert.equal(stamped1[4].indexb, 0);
      assert.equal(stamped1[4].indexc, 0);
      assert.equal(stamped1[15].itemaProp, 'a');
      assert.equal(stamped1[15].indexa, 3);
      assert.equal(stamped1[16], undefined);
      done();
    });
  });

});

suite('reacting to changes, view sort with filter', function() {

  var unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    unconfigured.$.el1.$.repeater.sort = function(a, b) {
      return b.prop == a.prop ? 0 : b.prop < a.prop ? -1 : 1;
    };
    unconfigured.$.el1.$.repeater.filter = function(o) {
      return o.prop.indexOf('2') < 0;
    };
    flush();
  });

  test('push', function(done) {
    unconfigured.push('items', {prop: 'a'}, {prop: 'item*2'}, {prop: 'z'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[26].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[26].indexa, 2);
      assert.equal(stamped1[26].indexb, 2);
      assert.equal(stamped1[26].indexc, 2);
      assert.equal(stamped1[27].itemaProp, 'a');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[28], undefined);
      done();
    });
  });

  test('pop', function(done) {
    unconfigured.pop('items');
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[12].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[12].indexa, 0);
      assert.equal(stamped1[12].indexb, 2);
      assert.equal(stamped1[12].indexc, 2);
      assert.equal(stamped1[13], undefined);
      done();
    });
  });

  test('unshift', function(done) {
    unconfigured.unshift('items', {prop: 'a'}, {prop: 'item*2'}, {prop: 'z'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[26].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[26].indexa, 2);
      assert.equal(stamped1[26].indexb, 2);
      assert.equal(stamped1[26].indexc, 2);
      assert.equal(stamped1[27].itemaProp, 'a');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[28], undefined);
      done();
    });
  });

  test('shift', function(done) {
    unconfigured.shift('items');
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[12].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[12].indexa, 0);
      assert.equal(stamped1[12].indexb, 2);
      assert.equal(stamped1[12].indexc, 2);
      assert.equal(stamped1[13], undefined);
      done();
    });
  });

  test('splice - remove from middle', function(done) {
    unconfigured.splice('items', 1, 1)[0];
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'prop-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itembProp, 'prop-1-1');
      assert.equal(stamped1[14].indexa, 1);
      assert.equal(stamped1[14].indexb, 0);
      assert.equal(stamped1[15].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[15].indexa, 1);
      assert.equal(stamped1[15].indexb, 0);
      assert.equal(stamped1[15].indexc, 0);
      done();
    });
  });

  test('splice - add to middle', function(done) {
    unconfigured.splice('items', 1, 0, {prop: 'a'}, {prop: 'item*2'}, {prop: 'z'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[26].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[26].indexa, 2);
      assert.equal(stamped1[26].indexb, 2);
      assert.equal(stamped1[26].indexc, 2);
      assert.equal(stamped1[27].itemaProp, 'a');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[28], undefined);
      done();
    });
  });

  test('splice - replace in middle', function(done) {
    unconfigured.splice('items', 1, 2, {prop: 'a'}, {prop: 'item*2'}, {prop: 'z'});
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-1');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-1-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[13].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[13].indexb, 2);
      assert.equal(stamped1[13].indexc, 2);
      assert.equal(stamped1[14].itemaProp, 'a');
      assert.equal(stamped1[14].indexa, 2);
      assert.equal(stamped1[15], undefined);
      done();
    });
  });

});

suite('reacting to changes, array sort (direct mutation)', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured-mutable');
    unconfigured.items = window.getData();
    flush();
  });

  test('push', function(done) {
    unconfigured.$.el1.domUpdateHandlerCount = 0;
    unconfigured.items.push({prop: 'new-1'}, {prop: 'new-2'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[38].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[38].indexa, 2);
      assert.equal(stamped1[38].indexb, 2);
      assert.equal(stamped1[38].indexc, 2);
      assert.equal(stamped1[39].itemaProp, 'new-1');
      assert.equal(stamped1[40].itemaProp, 'new-2');
      assert.equal(stamped1[41], undefined);
      assert.equal(stamped2[38].itemcProp, 'prop-3-3-3');
      assert.equal(stamped2[38].indexa, 2);
      assert.equal(stamped2[38].indexb, 2);
      assert.equal(stamped2[38].indexc, 2);
      assert.equal(stamped2[39].itemaProp, 'new-1');
      assert.equal(stamped2[40].itemaProp, 'new-2');
      assert.equal(stamped2[41], undefined);
      assert.equal(unconfigured.$.el1.domUpdateHandlerCount, 1);
      done();
    });
  });

  test('pop', function(done) {
    unconfigured.items.pop();
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[25].itemcProp, 'prop-2-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      assert.equal(stamped2[25].itemcProp, 'prop-2-3-3');
      assert.equal(stamped2[25].indexa, 1);
      assert.equal(stamped2[25].indexb, 2);
      assert.equal(stamped2[25].indexc, 2);
      assert.equal(stamped2[26], undefined);
      done();
    });
  });

  test('unshift', function(done) {
    unconfigured.items.unshift({prop: 'new-1'}, {prop: 'new-2'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'new-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'new-2');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itemaProp, 'prop-1');
      assert.equal(stamped1[2].indexa, 2);
      assert.equal(stamped2[0].itemaProp, 'new-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped2[1].itemaProp, 'new-2');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped2[2].itemaProp, 'prop-1');
      assert.equal(stamped2[2].indexa, 2);
      done();
    });
  });

  test('shift', function(done) {
    unconfigured.items.shift();
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-2');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-2-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-2-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped2[0].itemaProp, 'prop-2');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-2-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-2-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      done();
    });
  });

  test('splice - remove from middle', function(done) {
    unconfigured.items.splice(1, 1)[0];
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'prop-3');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itembProp, 'prop-3-1');
      assert.equal(stamped1[14].indexa, 1);
      assert.equal(stamped1[14].indexb, 0);
      assert.equal(stamped1[15].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[15].indexa, 1);
      assert.equal(stamped1[15].indexb, 0);
      assert.equal(stamped1[15].indexc, 0);
      assert.equal(stamped2[0].itemaProp, 'prop-1');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-1-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      assert.equal(stamped2[13].itemaProp, 'prop-3');
      assert.equal(stamped2[13].indexa, 1);
      assert.equal(stamped2[14].itembProp, 'prop-3-1');
      assert.equal(stamped2[14].indexa, 1);
      assert.equal(stamped2[14].indexb, 0);
      assert.equal(stamped2[15].itemcProp, 'prop-3-1-1');
      assert.equal(stamped2[15].indexa, 1);
      assert.equal(stamped2[15].indexb, 0);
      assert.equal(stamped2[15].indexc, 0);
      done();
    });
  });

  test('splice - add to middle', function(done) {
    unconfigured.items.splice(1, 0, {prop: 'new-1'}, {prop: 'new-2'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'new-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itemaProp, 'new-2');
      assert.equal(stamped1[14].indexa, 2);
      assert.equal(stamped1[15].itemaProp, 'prop-2');
      assert.equal(stamped1[15].indexa, 3);
      assert.equal(stamped1[16].itembProp, 'prop-2-1');
      assert.equal(stamped1[16].indexa, 3);
      assert.equal(stamped1[16].indexb, 0);
      assert.equal(stamped1[17].itemcProp, 'prop-2-1-1');
      assert.equal(stamped1[17].indexa, 3);
      assert.equal(stamped1[17].indexb, 0);
      assert.equal(stamped1[17].indexc, 0);
      assert.equal(stamped2[0].itemaProp, 'prop-1');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-1-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      assert.equal(stamped2[13].itemaProp, 'new-1');
      assert.equal(stamped2[13].indexa, 1);
      assert.equal(stamped2[14].itemaProp, 'new-2');
      assert.equal(stamped2[14].indexa, 2);
      assert.equal(stamped2[15].itemaProp, 'prop-2');
      assert.equal(stamped2[15].indexa, 3);
      assert.equal(stamped2[16].itembProp, 'prop-2-1');
      assert.equal(stamped2[16].indexa, 3);
      assert.equal(stamped2[16].indexb, 0);
      assert.equal(stamped2[17].itemcProp, 'prop-2-1-1');
      assert.equal(stamped2[17].indexa, 3);
      assert.equal(stamped2[17].indexb, 0);
      assert.equal(stamped2[17].indexc, 0);
      done();
    });
  });

  test('splice - replace in middle', function(done) {
    unconfigured.items.splice(1, 2, {prop: 'new-1'}, {prop: 'new-2'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 2 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'new-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itemaProp, 'new-2');
      assert.equal(stamped1[14].indexa, 2);
      assert.equal(stamped1[15], undefined);
      assert.equal(stamped2[0].itemaProp, 'prop-1');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-1-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      assert.equal(stamped2[13].itemaProp, 'new-1');
      assert.equal(stamped2[13].indexa, 1);
      assert.equal(stamped2[14].itemaProp, 'new-2');
      assert.equal(stamped2[14].indexa, 2);
      assert.equal(stamped2[15], undefined);
      done();
    });
  });

  test('change graph', function(done) {
    unconfigured.items[2].items[2].items[2].prop = 'changed!';
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      var stamped2 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped2.length, 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped2[0].itemaProp, 'prop-1');
      assert.equal(stamped2[0].indexa, 0);
      assert.equal(stamped2[1].itembProp, 'prop-1-1');
      assert.equal(stamped2[1].indexa, 0);
      assert.equal(stamped2[1].indexb, 0);
      assert.equal(stamped2[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped2[2].indexa, 0);
      assert.equal(stamped2[2].indexb, 0);
      assert.equal(stamped2[2].indexc, 0);
      assert.equal(stamped1[38].itemcProp, 'changed!');
      assert.equal(stamped1[38].indexa, 2);
      assert.equal(stamped1[38].indexb, 2);
      assert.equal(stamped1[38].indexc, 2);
      assert.equal(stamped1[39], undefined);
      assert.equal(stamped2[38].itemcProp, 'changed!');
      assert.equal(stamped2[38].indexa, 2);
      assert.equal(stamped2[38].indexb, 2);
      assert.equal(stamped2[38].indexc, 2);
      assert.equal(stamped2[39], undefined);
      done();
    });
  });

});

suite('reacting to changes, array sort with filter (direct mutation)', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured-mutable');
    unconfigured.items = window.getData();
    unconfigured.$.el1.$.repeater.filter = function(o) {
      return o.prop.indexOf('2') < 0;
    };
    flush();
  });

  test('push', function(done) {
    unconfigured.items.push({prop: 'new-1'}, {prop: 'new-2'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[23].itemcProp, 'prop-3-3-1');
      assert.equal(stamped1[23].indexa, 1);
      assert.equal(stamped1[23].indexb, 2);
      assert.equal(stamped1[23].indexc, 0);
      assert.equal(stamped1[24].itemcProp, 'prop-3-3-2');
      assert.equal(stamped1[24].indexa, 1);
      assert.equal(stamped1[24].indexb, 2);
      assert.equal(stamped1[24].indexc, 1);
      assert.equal(stamped1[25].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26].itemaProp, 'new-1');
      assert.equal(stamped1[26].indexa, 2);
      assert.equal(stamped1[27], undefined);
      done();
    });
  });

  test('pop', function(done) {
    unconfigured.items.pop();
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[10].itemcProp, 'prop-1-3-1');
      assert.equal(stamped1[10].indexa, 0);
      assert.equal(stamped1[10].indexb, 2);
      assert.equal(stamped1[10].indexc, 0);
      assert.equal(stamped1[11].itemcProp, 'prop-1-3-2');
      assert.equal(stamped1[11].indexa, 0);
      assert.equal(stamped1[11].indexb, 2);
      assert.equal(stamped1[11].indexc, 1);
      assert.equal(stamped1[12].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[12].indexa, 0);
      assert.equal(stamped1[12].indexb, 2);
      assert.equal(stamped1[12].indexc, 2);
      assert.equal(stamped1[13], undefined);
      done();
    });
  });

  test('unshift', function(done) {
    unconfigured.items.unshift({prop: 'new-1'}, {prop: 'new-2'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'new-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-1');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-1-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      done();
    });
  });

  test('shift', function(done) {
    unconfigured.items.shift();
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[10].itemcProp, 'prop-3-3-1');
      assert.equal(stamped1[10].indexa, 0);
      assert.equal(stamped1[10].indexb, 2);
      assert.equal(stamped1[10].indexc, 0);
      assert.equal(stamped1[11].itemcProp, 'prop-3-3-2');
      assert.equal(stamped1[11].indexa, 0);
      assert.equal(stamped1[11].indexb, 2);
      assert.equal(stamped1[11].indexc, 1);
      assert.equal(stamped1[12].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[12].indexa, 0);
      assert.equal(stamped1[12].indexb, 2);
      assert.equal(stamped1[12].indexc, 2);
      assert.equal(stamped1[13], undefined);
      done();
    });
  });

  test('splice - remove from middle', function(done) {
    unconfigured.items.splice(1, 1);
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[23].itemcProp, 'prop-3-3-1');
      assert.equal(stamped1[23].indexa, 1);
      assert.equal(stamped1[23].indexb, 2);
      assert.equal(stamped1[23].indexc, 0);
      assert.equal(stamped1[24].itemcProp, 'prop-3-3-2');
      assert.equal(stamped1[24].indexa, 1);
      assert.equal(stamped1[24].indexb, 2);
      assert.equal(stamped1[24].indexc, 1);
      assert.equal(stamped1[25].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      done();
    });
  });

  test('splice - add to middle', function(done) {
    unconfigured.items.splice(1, 0, {prop: 'new-1'}, {prop: 'new-2'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'new-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itemaProp, 'prop-3');
      assert.equal(stamped1[14].indexa, 2);
      assert.equal(stamped1[15].itembProp, 'prop-3-1');
      assert.equal(stamped1[15].indexa, 2);
      assert.equal(stamped1[15].indexb, 0);
      assert.equal(stamped1[16].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[16].indexa, 2);
      assert.equal(stamped1[16].indexb, 0);
      assert.equal(stamped1[16].indexc, 0);
      done();
    });
  });

  test('splice - replace in middle', function(done) {
    unconfigured.items.splice(1, 2, {prop: 'new-1'}, {prop: 'new-2'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'new-1');
      assert.equal(stamped1[14], undefined);
      done();
    });
  });

});

suite('reacting to changes, view sort (direct mutation)', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured-mutable');
    unconfigured.items = window.getData();
    unconfigured.$.el1.$.repeater.sort = function(a, b) {
      return b.prop == a.prop ? 0 : b.prop < a.prop ? -1 : 1;
    };
    flush();
  });

  test('push', function(done) {
    unconfigured.items.push({prop: 'a'}, {prop: 'prop-1*'}, {prop: 'z'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[27].itemaProp, 'prop-1*');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[40].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[40].indexa, 4);
      assert.equal(stamped1[40].indexb, 2);
      assert.equal(stamped1[40].indexc, 2);
      assert.equal(stamped1[41].itemaProp, 'a');
      assert.equal(stamped1[41].indexa, 5);
      done();
    });
  });

  test('pop', function(done) {
    unconfigured.items.pop();
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[25].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      done();
    });
  });

  test('unshift', function(done) {
    unconfigured.items.unshift({prop: 'a'}, {prop: 'prop-1*'}, {prop: 'z'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[27].itemaProp, 'prop-1*');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[40].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[40].indexa, 4);
      assert.equal(stamped1[40].indexb, 2);
      assert.equal(stamped1[40].indexc, 2);
      assert.equal(stamped1[41].itemaProp, 'a');
      assert.equal(stamped1[41].indexa, 5);
      done();
    });
  });

  test('shift', function(done) {
    unconfigured.items.shift();
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[23].itemcProp, 'prop-2-3-1');
      assert.equal(stamped1[23].indexa, 1);
      assert.equal(stamped1[23].indexb, 2);
      assert.equal(stamped1[23].indexc, 0);
      assert.equal(stamped1[24].itemcProp, 'prop-2-3-2');
      assert.equal(stamped1[24].indexa, 1);
      assert.equal(stamped1[24].indexb, 2);
      assert.equal(stamped1[24].indexc, 1);
      assert.equal(stamped1[25].itemcProp, 'prop-2-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      done();
    });
  });

  test('splice - remove from middle', function(done) {
    unconfigured.items.splice(1, 1)[0];
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'prop-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itembProp, 'prop-1-1');
      assert.equal(stamped1[14].indexa, 1);
      assert.equal(stamped1[14].indexb, 0);
      assert.equal(stamped1[15].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[15].indexa, 1);
      assert.equal(stamped1[15].indexb, 0);
      assert.equal(stamped1[15].indexc, 0);
      assert.equal(stamped1[23].itemcProp, 'prop-1-3-1');
      assert.equal(stamped1[23].indexa, 1);
      assert.equal(stamped1[23].indexb, 2);
      assert.equal(stamped1[23].indexc, 0);
      assert.equal(stamped1[24].itemcProp, 'prop-1-3-2');
      assert.equal(stamped1[24].indexa, 1);
      assert.equal(stamped1[24].indexb, 2);
      assert.equal(stamped1[24].indexc, 1);
      assert.equal(stamped1[25].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[25].indexa, 1);
      assert.equal(stamped1[25].indexb, 2);
      assert.equal(stamped1[25].indexc, 2);
      assert.equal(stamped1[26], undefined);
      done();
    });
  });

  test('splice - add to middle', function(done) {
    unconfigured.items.splice(1, 0, {prop: 'a'}, {prop: 'prop-1*'}, {prop: 'z'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 3 + 3*3 + 3*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[27].itemaProp, 'prop-1*');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[40].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[40].indexa, 4);
      assert.equal(stamped1[40].indexb, 2);
      assert.equal(stamped1[40].indexc, 2);
      assert.equal(stamped1[41].itemaProp, 'a');
      assert.equal(stamped1[41].indexa, 5);
      done();
    });
  });

  test('splice - replace in middle', function(done) {
    unconfigured.items.splice(1, 2, {prop: 'a'}, {prop: 'prop-1*'}, {prop: 'z'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 3 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-1*');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itemaProp, 'prop-1');
      assert.equal(stamped1[2].indexa, 2);
      assert.equal(stamped1[3].itembProp, 'prop-1-1');
      assert.equal(stamped1[3].indexa, 2);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[4].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[4].indexa, 2);
      assert.equal(stamped1[4].indexb, 0);
      assert.equal(stamped1[4].indexc, 0);
      assert.equal(stamped1[15].itemaProp, 'a');
      assert.equal(stamped1[15].indexa, 3);
      assert.equal(stamped1[16], undefined);
      done();
    });
  });

});

suite('reacting to changes, view sort with filter (direct mutation)', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured-mutable');
    unconfigured.items = window.getData();
    unconfigured.$.el1.$.repeater.sort = function(a, b) {
      return b.prop == a.prop ? 0 : b.prop < a.prop ? -1 : 1;
    };
    unconfigured.$.el1.$.repeater.filter = function(o) {
      return o.prop.indexOf('2') < 0;
    };
    flush();
  });

  test('push', function(done) {
    unconfigured.items.push({prop: 'a'}, {prop: 'item*2'}, {prop: 'z'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[26].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[26].indexa, 2);
      assert.equal(stamped1[26].indexb, 2);
      assert.equal(stamped1[26].indexc, 2);
      assert.equal(stamped1[27].itemaProp, 'a');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[28], undefined);
      done();
    });
  });

  test('pop', function(done) {
    unconfigured.items.pop();
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-1');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-1-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[12].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[12].indexa, 0);
      assert.equal(stamped1[12].indexb, 2);
      assert.equal(stamped1[12].indexc, 2);
      assert.equal(stamped1[13], undefined);
      done();
    });
  });

  test('unshift', function(done) {
    unconfigured.items.unshift({prop: 'a'}, {prop: 'item*2'}, {prop: 'z'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[26].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[26].indexa, 2);
      assert.equal(stamped1[26].indexb, 2);
      assert.equal(stamped1[26].indexc, 2);
      assert.equal(stamped1[27].itemaProp, 'a');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[28], undefined);
      done();
    });
  });

  test('shift', function(done) {
    unconfigured.items.shift();
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[12].itemcProp, 'prop-3-3-3');
      assert.equal(stamped1[12].indexa, 0);
      assert.equal(stamped1[12].indexb, 2);
      assert.equal(stamped1[12].indexc, 2);
      assert.equal(stamped1[13], undefined);
      done();
    });
  });

  test('splice - remove from middle', function(done) {
    unconfigured.items.splice(1, 1)[0];
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'prop-3');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itembProp, 'prop-3-1');
      assert.equal(stamped1[1].indexa, 0);
      assert.equal(stamped1[1].indexb, 0);
      assert.equal(stamped1[2].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[2].indexa, 0);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[2].indexc, 0);
      assert.equal(stamped1[13].itemaProp, 'prop-1');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[14].itembProp, 'prop-1-1');
      assert.equal(stamped1[14].indexa, 1);
      assert.equal(stamped1[14].indexb, 0);
      assert.equal(stamped1[15].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[15].indexa, 1);
      assert.equal(stamped1[15].indexb, 0);
      assert.equal(stamped1[15].indexc, 0);
      done();
    });
  });

  test('splice - add to middle', function(done) {
    unconfigured.items.splice(1, 0, {prop: 'a'}, {prop: 'item*2'}, {prop: 'z'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 2 + 2*3 + 2*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-3');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-3-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-3-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[26].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[26].indexa, 2);
      assert.equal(stamped1[26].indexb, 2);
      assert.equal(stamped1[26].indexc, 2);
      assert.equal(stamped1[27].itemaProp, 'a');
      assert.equal(stamped1[27].indexa, 3);
      assert.equal(stamped1[28], undefined);
      done();
    });
  });

  test('splice - replace in middle', function(done) {
    unconfigured.items.splice(1, 2, {prop: 'a'}, {prop: 'item*2'}, {prop: 'z'});
    unconfigured.items = unconfigured.items;
    setTimeout(function() {
      var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1.length, 2 + 1 + 1*3 + 1*3*3, 'total stamped count incorrect');
      assert.equal(stamped1[0].itemaProp, 'z');
      assert.equal(stamped1[0].indexa, 0);
      assert.equal(stamped1[1].itemaProp, 'prop-1');
      assert.equal(stamped1[1].indexa, 1);
      assert.equal(stamped1[2].itembProp, 'prop-1-1');
      assert.equal(stamped1[2].indexa, 1);
      assert.equal(stamped1[2].indexb, 0);
      assert.equal(stamped1[3].itemcProp, 'prop-1-1-1');
      assert.equal(stamped1[3].indexa, 1);
      assert.equal(stamped1[3].indexb, 0);
      assert.equal(stamped1[3].indexc, 0);
      assert.equal(stamped1[13].itemcProp, 'prop-1-3-3');
      assert.equal(stamped1[13].indexa, 1);
      assert.equal(stamped1[13].indexb, 2);
      assert.equal(stamped1[13].indexc, 2);
      assert.equal(stamped1[14].itemaProp, 'a');
      assert.equal(stamped1[14].indexa, 2);
      assert.equal(stamped1[15], undefined);
      done();
    });
  });

});

suite('multiple mutations in same turn', function() {

  let primitive;

  setup(function() {
    primitive = fixture('primitive');
    flush();
  });

  test('array sort, no filter: unshift+pop', function(done) {
    // repeater 1
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'a');
    assert.equal(stamped1[1].itemaProp, 'b');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'd');
    assert.equal(stamped1[4].itemaProp, 'e');
    primitive.unshift('items', 'new1');
    primitive.pop('items');
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'new1');
      assert.equal(stamped1[1].itemaProp, 'a');
      assert.equal(stamped1[2].itemaProp, 'b');
      assert.equal(stamped1[3].itemaProp, 'c');
      assert.equal(stamped1[4].itemaProp, 'd');
      primitive.unshift('items', 'new2');
      primitive.pop('items');
      primitive.unshift('items', 'new3');
      primitive.pop('items');
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'new3');
        assert.equal(stamped1[1].itemaProp, 'new2');
        assert.equal(stamped1[2].itemaProp, 'new1');
        assert.equal(stamped1[3].itemaProp, 'a');
        assert.equal(stamped1[4].itemaProp, 'b');
        primitive.shift('items');
        primitive.push('items', 'c');
        setTimeout(function() {
          stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
          assert.equal(stamped1[0].itemaProp, 'new2');
          assert.equal(stamped1[1].itemaProp, 'new1');
          assert.equal(stamped1[2].itemaProp, 'a');
          assert.equal(stamped1[3].itemaProp, 'b');
          assert.equal(stamped1[4].itemaProp, 'c');
          primitive.shift('items');
          primitive.push('items', 'd');
          primitive.shift('items');
          primitive.push('items', 'e');
          setTimeout(function() {
            stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
            assert.equal(stamped1[0].itemaProp, 'a');
            assert.equal(stamped1[1].itemaProp, 'b');
            assert.equal(stamped1[2].itemaProp, 'c');
            assert.equal(stamped1[3].itemaProp, 'd');
            assert.equal(stamped1[4].itemaProp, 'e');
            done();
          });
        });
      });
    });
  });

  test('array sort, no filter: insertions', function(done) {
    // repeater 1
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'a');
    assert.equal(stamped1[1].itemaProp, 'b');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'd');
    assert.equal(stamped1[4].itemaProp, 'e');
    primitive.splice('items', 1, 0, 'new2', 'new3');
    primitive.splice('items', 0, 0, 'new1');
    primitive.splice('items', 8, 0, 'new5');
    primitive.splice('items', 5, 0, 'new4');
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'new1');
      assert.equal(stamped1[1].itemaProp, 'a');
      assert.equal(stamped1[2].itemaProp, 'new2');
      assert.equal(stamped1[3].itemaProp, 'new3');
      assert.equal(stamped1[4].itemaProp, 'b');
      assert.equal(stamped1[5].itemaProp, 'new4');
      assert.equal(stamped1[6].itemaProp, 'c');
      assert.equal(stamped1[7].itemaProp, 'd');
      assert.equal(stamped1[8].itemaProp, 'e');
      assert.equal(stamped1[9].itemaProp, 'new5');
      arrayDelete(primitive, 'items',  'new5');
      arrayDelete(primitive, 'items',  'new3');
      arrayDelete(primitive, 'items',  'new1');
      arrayDelete(primitive, 'items',  'new2');
      arrayDelete(primitive, 'items',  'new4');
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'a');
        assert.equal(stamped1[1].itemaProp, 'b');
        assert.equal(stamped1[2].itemaProp, 'c');
        assert.equal(stamped1[3].itemaProp, 'd');
        assert.equal(stamped1[4].itemaProp, 'e');
        done();
      });
    });
  });

  test('array sort, no filter: insert & remove', function(done) {
    // repeater 1
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'a');
    assert.equal(stamped1[1].itemaProp, 'b');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'd');
    assert.equal(stamped1[4].itemaProp, 'e');
    primitive.splice('items', 1, 0, 'new1', 'new2', 'new3');
    primitive.splice('items', 3, 1);
    primitive.splice('items', 4, 0, 'new4', 'new5', 'new6');
    primitive.splice('items', 4, 2);
    primitive.splice('items', 4, 1);
    primitive.splice('items', 1, 2);
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'a');
      assert.equal(stamped1[1].itemaProp, 'b');
      assert.equal(stamped1[2].itemaProp, 'c');
      assert.equal(stamped1[3].itemaProp, 'd');
      assert.equal(stamped1[4].itemaProp, 'e');
      done();
    });
  });

  test('array sort, filtered: unshift+pop', function(done) {
    primitive.$.repeater1.filter = function(s) {
      return s.indexOf('new2') < 0;
    };
    flush();
    // repeater 1
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'a');
    assert.equal(stamped1[1].itemaProp, 'b');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'd');
    assert.equal(stamped1[4].itemaProp, 'e');
    primitive.unshift('items', 'new1');
    primitive.pop('items');
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'new1');
      assert.equal(stamped1[1].itemaProp, 'a');
      assert.equal(stamped1[2].itemaProp, 'b');
      assert.equal(stamped1[3].itemaProp, 'c');
      assert.equal(stamped1[4].itemaProp, 'd');
      primitive.unshift('items', 'new2');
      primitive.pop('items');
      primitive.unshift('items', 'new3');
      primitive.pop('items');
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'new3');
        assert.equal(stamped1[1].itemaProp, 'new1');
        assert.equal(stamped1[2].itemaProp, 'a');
        assert.equal(stamped1[3].itemaProp, 'b');
        primitive.shift('items');
        primitive.push('items', 'c');
        setTimeout(function() {
          stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
          assert.equal(stamped1[0].itemaProp, 'new1');
          assert.equal(stamped1[1].itemaProp, 'a');
          assert.equal(stamped1[2].itemaProp, 'b');
          assert.equal(stamped1[3].itemaProp, 'c');
          primitive.shift('items');
          primitive.push('items', 'd');
          primitive.shift('items');
          primitive.push('items', 'e');
          setTimeout(function() {
            stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
            assert.equal(stamped1[0].itemaProp, 'a');
            assert.equal(stamped1[1].itemaProp, 'b');
            assert.equal(stamped1[2].itemaProp, 'c');
            assert.equal(stamped1[3].itemaProp, 'd');
            assert.equal(stamped1[4].itemaProp, 'e');
            done();
          });
        });
      });
    });
  });

  test('array sort, filtered: insertions', function(done) {
    // repeater 1
    primitive.$.repeater1.filter = function(s) {
      return s.indexOf('new2') < 0;
    };
    flush();
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'a');
    assert.equal(stamped1[1].itemaProp, 'b');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'd');
    assert.equal(stamped1[4].itemaProp, 'e');
    primitive.splice('items', 1, 0, 'new2', 'new3');
    primitive.splice('items', 0, 0, 'new1');
    primitive.splice('items', 8, 0, 'new5');
    primitive.splice('items', 5, 0, 'new4');
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'new1');
      assert.equal(stamped1[1].itemaProp, 'a');
      assert.equal(stamped1[2].itemaProp, 'new3');
      assert.equal(stamped1[3].itemaProp, 'b');
      assert.equal(stamped1[4].itemaProp, 'new4');
      assert.equal(stamped1[5].itemaProp, 'c');
      assert.equal(stamped1[6].itemaProp, 'd');
      assert.equal(stamped1[7].itemaProp, 'e');
      assert.equal(stamped1[8].itemaProp, 'new5');
      arrayDelete(primitive, 'items',  'new5');
      arrayDelete(primitive, 'items',  'new3');
      arrayDelete(primitive, 'items',  'new1');
      arrayDelete(primitive, 'items',  'new2');
      arrayDelete(primitive, 'items',  'new4');
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'a');
        assert.equal(stamped1[1].itemaProp, 'b');
        assert.equal(stamped1[2].itemaProp, 'c');
        assert.equal(stamped1[3].itemaProp, 'd');
        assert.equal(stamped1[4].itemaProp, 'e');
        done();
      });
    });
  });

  test('array sort, filtered: insert & remove', function(done) {
    // repeater 1
    primitive.$.repeater1.filter = function(s) {
      return s.indexOf('new2') < 0;
    };
    flush();
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'a');
    assert.equal(stamped1[1].itemaProp, 'b');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'd');
    assert.equal(stamped1[4].itemaProp, 'e');
    primitive.splice('items', 1, 0, 'new1', 'new2', 'new3');
    primitive.splice('items', 3, 1);
    primitive.splice('items', 4, 0, 'new4', 'new5', 'new6');
    primitive.splice('items', 4, 2);
    primitive.splice('items', 4, 1);
    primitive.splice('items', 1, 2);
    primitive.$.repeater1.filter = null;
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'a');
      assert.equal(stamped1[1].itemaProp, 'b');
      assert.equal(stamped1[2].itemaProp, 'c');
      assert.equal(stamped1[3].itemaProp, 'd');
      assert.equal(stamped1[4].itemaProp, 'e');
      done();
    });
  });

  test('view sort, no filter: unshift+pop', function(done) {
    primitive.$.repeater1.sort = function(a, b) {
      return b.localeCompare(a);
    };
    flush();
    setTimeout(function() {
      // repeater 1
      var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'e');
      assert.equal(stamped1[1].itemaProp, 'd');
      assert.equal(stamped1[2].itemaProp, 'c');
      assert.equal(stamped1[3].itemaProp, 'b');
      assert.equal(stamped1[4].itemaProp, 'a');
      primitive.unshift('items', 'new1');
      primitive.pop('items');
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'new1');
        assert.equal(stamped1[1].itemaProp, 'd');
        assert.equal(stamped1[2].itemaProp, 'c');
        assert.equal(stamped1[3].itemaProp, 'b');
        assert.equal(stamped1[4].itemaProp, 'a');
        primitive.unshift('items', 'new2');
        primitive.pop('items');
        primitive.unshift('items', 'new3');
        primitive.pop('items');
        setTimeout(function() {
          stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
          assert.equal(stamped1[0].itemaProp, 'new3');
          assert.equal(stamped1[1].itemaProp, 'new2');
          assert.equal(stamped1[2].itemaProp, 'new1');
          assert.equal(stamped1[3].itemaProp, 'b');
          assert.equal(stamped1[4].itemaProp, 'a');
          primitive.shift('items');
          primitive.push('items', 'c');
          setTimeout(function() {
            stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
            assert.equal(stamped1[0].itemaProp, 'new2');
            assert.equal(stamped1[1].itemaProp, 'new1');
            assert.equal(stamped1[2].itemaProp, 'c');
            assert.equal(stamped1[3].itemaProp, 'b');
            assert.equal(stamped1[4].itemaProp, 'a');
            primitive.shift('items');
            primitive.push('items', 'd');
            primitive.shift('items');
            primitive.push('items', 'e');
            setTimeout(function() {
              stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
              assert.equal(stamped1[0].itemaProp, 'e');
              assert.equal(stamped1[1].itemaProp, 'd');
              assert.equal(stamped1[2].itemaProp, 'c');
              assert.equal(stamped1[3].itemaProp, 'b');
              assert.equal(stamped1[4].itemaProp, 'a');
              done();
            });
          });
        });
      });
    });
  });

  test('view sort, no filter: insertions', function(done) {
    // repeater 1
    primitive.$.repeater1.sort = function(a, b) {
      return b.localeCompare(a);
    };
    flush();
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'e');
    assert.equal(stamped1[1].itemaProp, 'd');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'b');
    assert.equal(stamped1[4].itemaProp, 'a');
    primitive.splice('items', 1, 0, 'new2', 'new3');
    primitive.splice('items', 0, 0, 'new1');
    primitive.splice('items', 8, 0, 'new5');
    primitive.splice('items', 5, 0, 'new4');
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'new5');
      assert.equal(stamped1[1].itemaProp, 'new4');
      assert.equal(stamped1[2].itemaProp, 'new3');
      assert.equal(stamped1[3].itemaProp, 'new2');
      assert.equal(stamped1[4].itemaProp, 'new1');
      assert.equal(stamped1[5].itemaProp, 'e');
      assert.equal(stamped1[6].itemaProp, 'd');
      assert.equal(stamped1[7].itemaProp, 'c');
      assert.equal(stamped1[8].itemaProp, 'b');
      assert.equal(stamped1[9].itemaProp, 'a');
      arrayDelete(primitive, 'items',  'new5');
      arrayDelete(primitive, 'items',  'new3');
      arrayDelete(primitive, 'items',  'new1');
      arrayDelete(primitive, 'items',  'new2');
      arrayDelete(primitive, 'items',  'new4');
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'e');
        assert.equal(stamped1[1].itemaProp, 'd');
        assert.equal(stamped1[2].itemaProp, 'c');
        assert.equal(stamped1[3].itemaProp, 'b');
        assert.equal(stamped1[4].itemaProp, 'a');
        done();
      });
    });
  });

  test('view sort, no filter: insert & remove', function(done) {
    // repeater 1
    primitive.$.repeater1.sort = function(a, b) {
      return b.localeCompare(a);
    };
    flush();
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'e');
    assert.equal(stamped1[1].itemaProp, 'd');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'b');
    assert.equal(stamped1[4].itemaProp, 'a');
    primitive.splice('items', 1, 0, 'new1', 'new2', 'new3');
    primitive.splice('items', 3, 1);
    primitive.splice('items', 4, 0, 'new4', 'new5', 'new6');
    primitive.splice('items', 4, 2);
    primitive.splice('items', 4, 1);
    primitive.splice('items', 1, 2);
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'e');
      assert.equal(stamped1[1].itemaProp, 'd');
      assert.equal(stamped1[2].itemaProp, 'c');
      assert.equal(stamped1[3].itemaProp, 'b');
      assert.equal(stamped1[4].itemaProp, 'a');
      done();
    });
  });

  test('view sort, filtered: unshift+pop', function(done) {
    primitive.$.repeater1.sort = function(a, b) {
      return b.localeCompare(a);
    };
    primitive.$.repeater1.filter = function(s) {
      return s.indexOf('new2') < 0;
    };
    flush();
    setTimeout(function() {
      // repeater 1
      var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'e');
      assert.equal(stamped1[1].itemaProp, 'd');
      assert.equal(stamped1[2].itemaProp, 'c');
      assert.equal(stamped1[3].itemaProp, 'b');
      assert.equal(stamped1[4].itemaProp, 'a');
      primitive.unshift('items', 'new1');
      primitive.pop('items');
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'new1');
        assert.equal(stamped1[1].itemaProp, 'd');
        assert.equal(stamped1[2].itemaProp, 'c');
        assert.equal(stamped1[3].itemaProp, 'b');
        assert.equal(stamped1[4].itemaProp, 'a');
        primitive.unshift('items', 'new2');
        primitive.pop('items');
        primitive.unshift('items', 'new3');
        primitive.pop('items');
        setTimeout(function() {
          stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
          assert.equal(stamped1[0].itemaProp, 'new3');
          assert.equal(stamped1[1].itemaProp, 'new1');
          assert.equal(stamped1[2].itemaProp, 'b');
          assert.equal(stamped1[3].itemaProp, 'a');
          primitive.shift('items');
          primitive.push('items', 'c');
          setTimeout(function() {
            stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
            assert.equal(stamped1[0].itemaProp, 'new1');
            assert.equal(stamped1[1].itemaProp, 'c');
            assert.equal(stamped1[2].itemaProp, 'b');
            assert.equal(stamped1[3].itemaProp, 'a');
            primitive.shift('items');
            primitive.push('items', 'd');
            primitive.shift('items');
            primitive.push('items', 'e');
            setTimeout(function() {
              stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
              assert.equal(stamped1[0].itemaProp, 'e');
              assert.equal(stamped1[1].itemaProp, 'd');
              assert.equal(stamped1[2].itemaProp, 'c');
              assert.equal(stamped1[3].itemaProp, 'b');
              assert.equal(stamped1[4].itemaProp, 'a');
              done();
            });
          });
        });
      });
    });
  });

  test('view sort, filtered: insertions', function(done) {
    // repeater 1
    primitive.$.repeater1.sort = function(a, b) {
      return b.localeCompare(a);
    };
    primitive.$.repeater1.filter = function(s) {
      return s.indexOf('new2') < 0;
    };
    flush();
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'e');
    assert.equal(stamped1[1].itemaProp, 'd');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'b');
    assert.equal(stamped1[4].itemaProp, 'a');
    primitive.splice('items', 1, 0, 'new2', 'new3');
    primitive.splice('items', 0, 0, 'new1');
    primitive.splice('items', 8, 0, 'new5');
    primitive.splice('items', 5, 0, 'new4');
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'new5');
      assert.equal(stamped1[1].itemaProp, 'new4');
      assert.equal(stamped1[2].itemaProp, 'new3');
      assert.equal(stamped1[3].itemaProp, 'new1');
      assert.equal(stamped1[4].itemaProp, 'e');
      assert.equal(stamped1[5].itemaProp, 'd');
      assert.equal(stamped1[6].itemaProp, 'c');
      assert.equal(stamped1[7].itemaProp, 'b');
      assert.equal(stamped1[8].itemaProp, 'a');
      arrayDelete(primitive, 'items',  'new5');
      arrayDelete(primitive, 'items',  'new3');
      arrayDelete(primitive, 'items',  'new1');
      arrayDelete(primitive, 'items',  'new2');
      arrayDelete(primitive, 'items',  'new4');
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'e');
        assert.equal(stamped1[1].itemaProp, 'd');
        assert.equal(stamped1[2].itemaProp, 'c');
        assert.equal(stamped1[3].itemaProp, 'b');
        assert.equal(stamped1[4].itemaProp, 'a');
        done();
      });
    });
  });

  test('view sort, no filter: insert & remove', function(done) {
    // repeater 1
    primitive.$.repeater1.sort = function(a, b) {
      return b.localeCompare(a);
    };
    flush();
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'e');
    assert.equal(stamped1[1].itemaProp, 'd');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'b');
    assert.equal(stamped1[4].itemaProp, 'a');
    primitive.splice('items', 1, 0, 'new1', 'new2', 'new3');
    primitive.splice('items', 3, 1);
    primitive.splice('items', 4, 0, 'new4', 'new5', 'new6');
    primitive.splice('items', 4, 2);
    primitive.splice('items', 4, 1);
    primitive.splice('items', 1, 2);
    setTimeout(function() {
      stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped1[0].itemaProp, 'e');
      assert.equal(stamped1[1].itemaProp, 'd');
      assert.equal(stamped1[2].itemaProp, 'c');
      assert.equal(stamped1[3].itemaProp, 'b');
      assert.equal(stamped1[4].itemaProp, 'a');
      primitive.$.repeater1.filter = null;
      primitive.$.repeater1.sort = null;
      setTimeout(function() {
        stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        assert.equal(stamped1[0].itemaProp, 'a');
        assert.equal(stamped1[1].itemaProp, 'b');
        assert.equal(stamped1[2].itemaProp, 'c');
        assert.equal(stamped1[3].itemaProp, 'd');
        assert.equal(stamped1[4].itemaProp, 'e');
        done();
      });
    });
  });

});

suite('item changing on instance', function() {

  let primitive;

  setup(function() {
    primitive = fixture('primitive');
    flush();
  });

  test('primitive values - initial stamping', function() {
    // repeater 1
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'a');
    assert.equal(stamped1[1].itemaProp, 'b');
    assert.equal(stamped1[2].itemaProp, 'c');
    assert.equal(stamped1[3].itemaProp, 'd');
    assert.equal(stamped1[4].itemaProp, 'e');
    // repeater 2
    var stamped2 = primitive.$.container2.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped2[0].itemaProp, 'a');
    assert.equal(stamped2[1].itemaProp, 'b');
    assert.equal(stamped2[2].itemaProp, 'c');
    assert.equal(stamped2[3].itemaProp, 'd');
    assert.equal(stamped2[4].itemaProp, 'e');
  });

  test('primitive values - change from inside', function() {
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    var stamped2 = primitive.$.container2.querySelectorAll('*:not(template):not(dom-repeat)');
    var old = stamped1[2].itemaProp;
    // Change middle item
    stamped1[2].itemaProp = 'change!';
    assert.equal(primitive.items[2], 'change!');
    assert.equal(stamped2[2].itemaProp, 'change!');
    // Revert
    stamped2[2].itemaProp = old;
    assert.equal(primitive.items[2], 'c');
    assert.equal(stamped1[2].itemaProp, 'c');
  });

  test('primitive values - sorted', function() {
    // Apply sort
    primitive.$.repeater1.sort = function(a, b) {
      return b.localeCompare(a);
    };
    primitive.$.repeater1.render();
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    var stamped2 = primitive.$.container2.querySelectorAll('*:not(template):not(dom-repeat)');
    var old = stamped1[0].itemaProp;
    // Change last item (first rendered in #1, last rendered in #2)
    stamped1[0].itemaProp = 'change!';
    assert.equal(primitive.items[4], 'change!');
    assert.equal(stamped2[4].itemaProp, 'change!');
    // Revert
    stamped2[4].itemaProp = old;
    assert.equal(primitive.items[4], 'e');
    assert.equal(stamped1[0].itemaProp, 'e');
    primitive.$.repeater1.sort = null;
    primitive.$.repeater1.render();
  });

  test('structured values - initial stamping', function() {
    let unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    flush();
    var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    var stamped2 = unconfigured.$.el2.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[0].itemaProp, 'prop-1');
    assert.equal(stamped2[0].itemaProp, 'prop-1');
  });

  test('structured values - change from inside', function() {
    let unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    flush();
    var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    var stamped2 = unconfigured.$.el2.root.querySelectorAll('*:not(template):not(dom-repeat)');
    var old = unconfigured.items[0];
    // Change first item
    unconfigured.$.el1.$.repeater.__instances[0].itema = {prop: 'change!'};
    assert.equal(stamped1[0].itemaProp, 'change!');
    assert.equal(stamped2[0].itemaProp, 'change!');
    // Revert
    unconfigured.$.el2.$.repeater.__instances[0].itema = old;
    assert.equal(stamped1[0].itemaProp, 'prop-1');
    assert.equal(stamped2[0].itemaProp, 'prop-1');
  });

  test('structured values - sorted', function() {
    let unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    flush();
    unconfigured.$.el1.$.repeater.sort = function(a, b) {
      return b.prop == a.prop ? 0 : b.prop < a.prop ? -1 : 1;
    };
    unconfigured.$.el1.$.repeater.render();
    var stamped1 = unconfigured.$.el1.root.querySelectorAll('*:not(template):not(dom-repeat)');
    var stamped2 = unconfigured.$.el2.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped1[26].itemaProp, 'prop-1');
    assert.equal(stamped2[26].itemaProp, 'prop-3');
    var old = unconfigured.items[2];
    // Change last item (first rendered in #1, last rendered in #2)
    // (but only because `observe` doesn't catch this and cause the list to
    // re-sort; if it was, stamped1[26] should change)
    unconfigured.$.el1.$.repeater.__instances[0].itema = {prop: 'change!'};
    assert.equal(stamped1[0].itemaProp, 'change!');
    assert.equal(stamped2[26].itemaProp, 'change!');
    // Revert
    unconfigured.$.el2.$.repeater.__instances[2].itema = old;
    assert.equal(stamped1[0].itemaProp, 'prop-3');
    assert.equal(stamped2[26].itemaProp, 'prop-3');
    unconfigured.$.el1.$.repeater.sort = null;
    unconfigured.$.el1.$.repeater.render();
  });

});

suite('external change & notification', function() {

  test('in-place sort', function(done) {
    var items = [
      173, 166, 145, 755, 907,
      836, 564, 721, 540, 372,
      244, 145, 525, 958, 595,
      207, 103, 602, 769, 190];
    let primitive = fixture('primitive');
    primitive.items = items;
    setTimeout(function() {
      var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
      for (var i=0; i<items.length; i++) {
        assert.equal(stamped1[i].itemaProp, items[i]);
      }
      var prev = items.slice();
      items.sort();
      var splices = calculateSplices(items, prev);
      var change = {
        indexSplices: splices
      };
      primitive.set('items.splices', change);
      setTimeout(function() {
        var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
        for (var i=0; i<items.length; i++) {
          assert.equal(stamped1[i].itemaProp, items[i]);
        }
        done();
      });
    });
  });
});

suite('repeater API', function() {

  let unconfigured;

  setup(function() {
    unconfigured = fixture('unconfigured');
    unconfigured.items = window.getData();
    flush();
  });

  test('modelForElement', function() {
    var dom = unconfigured.$.el1.root;
    var stamped1 = dom.querySelectorAll('*:not(template):not(dom-repeat)');
    var repeater1 = dom.querySelector('dom-repeat[as=itema]');
    var repeater2 = dom.querySelector('dom-repeat[as=itemb]');
    var repeater3 = dom.querySelector('dom-repeat[as=itemc]');

    let model = repeater1.modelForElement(stamped1[4]);
    assert.equal(model.itema.prop, 'prop-1');

    model = repeater2.modelForElement(stamped1[4]);
    assert.equal(model.itema.prop, 'prop-1');
    assert.equal(model.itemb.prop, 'prop-1-1');

    model = repeater3.modelForElement(stamped1[4]);
    assert.equal(model.itema.prop, 'prop-1');
    assert.equal(model.itemb.prop, 'prop-1-1');
    assert.equal(model.itemc.prop, 'prop-1-1-3');
  });

  test('event.model && parentModel', function() {
    let el = document.createElement('x-repeat-with-if');
    document.body.appendChild(el);
    flush();
    let buttons = el.shadowRoot.querySelectorAll('button');
    let outer = el.shadowRoot.querySelector('#outer');
    let event;

    // First
    assert.equal(buttons[0].textContent, 'a');
    buttons[0].dispatchEvent(new CustomEvent('click', {bubbles: true}));
    assert.equal(el.handleClick.callCount, 1);
    event = el.handleClick.getCalls()[0].args[0];
    assert.equal(event._target, buttons[0]);
    assert.equal(event.model.item, el.items[0].items[0]);
    assert.equal(event.model.item.prop, 'a');
    assert.equal(event.model.parentModel.item, el.items[0]);
    assert.equal(event.model.parentModel.parentModel, el);
    assert.equal(outer.modelForElement(event._target).item, el.items[0]);

    // Second
    assert.equal(buttons[1].textContent, 'b');
    buttons[1].dispatchEvent(new CustomEvent('click', {bubbles: true}));
    assert.equal(el.handleClick.callCount, 2);
    event = el.handleClick.getCalls()[1].args[0];
    assert.equal(event._target, buttons[1]);
    assert.equal(event.model.item, el.items[0].items[1]);
    assert.equal(event.model.item.prop, 'b');
    assert.equal(event.model.parentModel.item, el.items[0]);
    assert.equal(event.model.parentModel.parentModel, el);
    assert.equal(outer.modelForElement(event._target).item, el.items[0]);

    // Third
    assert.equal(buttons[2].textContent, 'c');
    buttons[2].dispatchEvent(new CustomEvent('click', {bubbles: true}));
    assert.equal(el.handleClick.callCount, 3);
    event = el.handleClick.getCalls()[2].args[0];
    assert.equal(event._target, buttons[2]);
    assert.equal(event.model.item, el.items[1].items[0]);
    assert.equal(event.model.item.prop, 'c');
    assert.equal(event.model.parentModel.item, el.items[1]);
    assert.equal(event.model.parentModel.parentModel, el);
    assert.equal(outer.modelForElement(event._target).item, el.items[1]);

    // Fourth
    assert.equal(buttons[3].textContent, 'd');
    buttons[3].dispatchEvent(new CustomEvent('click', {bubbles: true}));
    assert.equal(el.handleClick.callCount, 4);
    event = el.handleClick.getCalls()[3].args[0];
    assert.equal(event._target, buttons[3]);
    assert.equal(event.model.item, el.items[1].items[1]);
    assert.equal(event.model.item.prop, 'd');
    assert.equal(event.model.parentModel.item, el.items[1]);
    assert.equal(event.model.parentModel.parentModel, el);
    assert.equal(outer.modelForElement(event._target).item, el.items[1]);

    document.body.removeChild(el);
  });

  test('indexForElement', function() {
    var dom = unconfigured.$.el1.root;
    var stamped1 = dom.querySelectorAll('*:not(template):not(dom-repeat)');
    var repeater1 = dom.querySelector('dom-repeat[as=itema]');
    var repeater2 = dom.querySelector('dom-repeat[as=itemb]');
    var repeater3 = dom.querySelector('dom-repeat[as=itemc]');

    assert.equal(repeater1.indexForElement(stamped1[4]), 0);
    assert.equal(repeater2.indexForElement(stamped1[4]), 0);
    assert.equal(repeater3.indexForElement(stamped1[4]), 2);
  });

  test('indexForElement', function() {
    var dom = unconfigured.$.el1.root;
    var stamped1 = dom.querySelectorAll('*:not(template):not(dom-repeat)');
    var repeater1 = dom.querySelector('dom-repeat[as=itema]');
    var repeater2 = dom.querySelector('dom-repeat[as=itemb]');
    var repeater3 = dom.querySelector('dom-repeat[as=itemc]');

    assert.equal(repeater1.itemForElement(stamped1[4]).prop, 'prop-1');
    assert.equal(repeater2.itemForElement(stamped1[4]).prop, 'prop-1-1');
    assert.equal(repeater3.itemForElement(stamped1[4]).prop, 'prop-1-1-3');
  });

  test('renderedItemCount', function() {
    let primitive = fixture('primitive');
    flush();
    var repeater1 = primitive.$.repeater1;
    primitive.items = [ 'a', 'b', 'c', 'd', 'e' ];
    repeater1.render();
    assert.equal(repeater1.renderedItemCount, 5, 'renderedItemCount is incorrect');
    repeater1.renderedItemCount = 0;
    assert.equal(repeater1.renderedItemCount, 5, 'renderedItemCount is writable');
    repeater1.filter = function(item) {
     return (item != 'a' && item != 'e');
    };
    repeater1.render();
    assert.equal(repeater1.renderedItemCount, 3, 'renderedItemCount incorrect after filter');
    // reset repeater
    repeater1.filter = undefined;
    repeater1.render();
  });

  test('_showHideChildren', function() {
    // Initially all showing
    let primitive = fixture('primitive');
    flush();
    var stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(getComputedStyle(stamped1[0]).display, 'block');
    assert.equal(getComputedStyle(stamped1[1]).display, 'block');
    assert.equal(getComputedStyle(stamped1[2]).display, 'block');
    assert.equal(getComputedStyle(stamped1[3]).display, 'block');
    assert.equal(getComputedStyle(stamped1[4]).display, 'block');

    // Hide all
    primitive.$.repeater1.__hideTemplateChildren__ = true;
    primitive.$.repeater1._showHideChildren(true);
    assert.equal(getComputedStyle(stamped1[0]).display, 'none');
    assert.equal(getComputedStyle(stamped1[1]).display, 'none');
    assert.equal(getComputedStyle(stamped1[2]).display, 'none');
    assert.equal(getComputedStyle(stamped1[3]).display, 'none');
    assert.equal(getComputedStyle(stamped1[4]).display, 'none');

    // Add one while hidden
    primitive.push('items', 'new 1');
    primitive.$.repeater1.render();
    stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(getComputedStyle(stamped1[0]).display, 'none');
    assert.equal(getComputedStyle(stamped1[1]).display, 'none');
    assert.equal(getComputedStyle(stamped1[2]).display, 'none');
    assert.equal(getComputedStyle(stamped1[3]).display, 'none');
    assert.equal(getComputedStyle(stamped1[4]).display, 'none');
    assert.equal(getComputedStyle(stamped1[5]).display, 'none');

    // Add one then unhide
    primitive.push('items', 'new 2');
    primitive.$.repeater1.__hideTemplateChildren__ = false;
    primitive.$.repeater1._showHideChildren(false);
    primitive.$.repeater1.render();
    stamped1 = primitive.$.container1.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(getComputedStyle(stamped1[0]).display, 'block');
    assert.equal(getComputedStyle(stamped1[1]).display, 'block');
    assert.equal(getComputedStyle(stamped1[2]).display, 'block');
    assert.equal(getComputedStyle(stamped1[3]).display, 'block');
    assert.equal(getComputedStyle(stamped1[4]).display, 'block');
    assert.equal(getComputedStyle(stamped1[5]).display, 'block');
    assert.equal(getComputedStyle(stamped1[6]).display, 'block');

    // Revert
    primitive.splice('items', 5, 2);
    primitive.$.repeater1.render();
  });

});


suite('limit', function() {

  // These tests verify correct operation of e.g. path notifications for a
  // pertially rendered list; it does this by monkey-patching the internal
  // `__calculateLimit` method to force the list to a certain length.
  // While they rely on some internal implementation details, the tests are
  // done this way because getting the list in a certain known state
  // during live chunking is tricky due to the feedback-based throttling.

  suiteSetup(() => {
    limited.$.repeater.__calculateLimit = function() {
      return this.__limit;
    };
  });

  suite('basic', function() {

    var checkItemOrder = function(stamped) {
      for (var i=0; i<stamped.length; i++) {
        assert.equal(parseInt(stamped[i].textContent), i);
      }
    };

    test('initial limit', function() {
      limited.items = limited.preppedItems;
      limited.$.repeater.__limit = 2;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 2);
      checkItemOrder(stamped);
    });

    test('change item paths in & out of limit', function() {
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      limited.outerProp = {prop: 'changed'};
      assert.equal(stamped[0].prop, 'changed');
      limited.set('items.0.prop', '0-changed');
      limited.set('items.3.prop', '3-changed');
      assert.equal(stamped[0].textContent, '0-changed');
      limited.set('outerProp.prop', 'changed again');
      assert.equal(stamped[0].prop, 'changed again');
    });

    test('increase limit', function() {
      // Increase limit
      limited.$.repeater.__limit = 10;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 10);
      checkItemOrder(stamped);
      assert.equal(stamped[3].prop, 'changed again');
      assert.equal(stamped[3].textContent, '3-changed');
      limited.set('items.0.prop', 0);
      limited.set('items.3.prop', 3);
      // Increase limit
      limited.$.repeater.__limit = 20;
      limited.$.repeater.render();
      stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 20);
      checkItemOrder(stamped);
    });

    test('decrease limit', function() {
      // Decrease limit
      limited.$.repeater.__limit = 15;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 15);
      checkItemOrder(stamped);
      // Decrease limit
      limited.$.repeater.__limit = 0;
      limited.$.repeater.render();
      stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 0);
    });

    test('negative limit', function() {
      limited.$.repeater.__limit = -10;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 0);
    });

  });

  suite('limit with sort', function() {

    var checkItemOrder = function(stamped) {
      for (var i=0; i<stamped.length; i++) {
        assert.equal(stamped[i].textContent, 19 - i);
      }
    };

    test('initial limit', function() {
      limited.$.repeater.__limit = 2;
      limited.$.repeater.sort = function(a, b) {
        return b.prop - a.prop;
      };
      limited.items = null;
      limited.$.repeater.render();
      limited.items = limited.preppedItems;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 2);
      checkItemOrder(stamped);
    });

    test('increase limit', function() {
      // Increase limit
      limited.$.repeater.__limit = 10;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 10);
      checkItemOrder(stamped);
      // Increase limit
      limited.$.repeater.__limit = 20;
      limited.$.repeater.render();
      stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 20);
      checkItemOrder(stamped);
    });

    test('decrease limit', function() {
      // Decrease limit
      limited.$.repeater.__limit = 15;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 15);
      checkItemOrder(stamped);
      // Decrease limit
      limited.$.repeater.__limit = 0;
      limited.$.repeater.render();
      stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 0);
    });

    test('negative limit', function() {
      limited.$.repeater.__limit = -10;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 0);
    });

  });

  suite('limit with filter', function() {

    var checkItemOrder = function(stamped) {
      for (var i=0; i<stamped.length; i++) {
        assert.equal(stamped[i].textContent, i * 2);
      }
    };

    test('initial limit', function() {
      var items = limited.items;
      limited.$.repeater.__limit = 2;
      limited.$.repeater.sort = null;
      limited.$.repeater.filter = function(a) {
        return (a.prop % 2) === 0;
      };
      limited.items = null;
      limited.$.repeater.render();
      limited.items = items;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 2);
      checkItemOrder(stamped);
    });

    test('increase limit', function() {
      // Increase limit
      limited.$.repeater.__limit = 5;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 5);
      checkItemOrder(stamped);
      // Increase limit
      limited.$.repeater.__limit = 10;
      limited.$.repeater.render();
      stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 10);
      checkItemOrder(stamped);
    });

    test('decrease limit', function() {
      // Decrease limit
      limited.$.repeater.__limit = 5;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 5);
      checkItemOrder(stamped);
      // Decrease limit
      limited.$.repeater.__limit = 0;
      limited.$.repeater.render();
      stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 0);
    });

    test('negative limit', function() {
      limited.$.repeater.__limit = -10;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 0);
    });

  });

  suite('limit with sort & filter', function() {

    var checkItemOrder = function(stamped) {
      for (var i=0; i<stamped.length; i++) {
        assert.equal(stamped[i].textContent, (9 - i) * 2);
      }
    };

    test('initial limit', function() {
      var items = limited.items;
      limited.$.repeater.__limit = 2;
      limited.$.repeater.sort = function(a, b) {
        return b.prop - a.prop;
      };
      limited.$.repeater.filter = function(a) {
        return (a.prop % 2) === 0;
      };
      limited.items = null;
      limited.$.repeater.render();
      limited.items = items;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 2);
      checkItemOrder(stamped);
    });

    test('increase limit', function() {
      // Increase limit
      limited.$.repeater.__limit = 5;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 5);
      checkItemOrder(stamped);
      // Increase limit
      limited.$.repeater.__limit = 10;
      limited.$.repeater.render();
      stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 10);
      checkItemOrder(stamped);
    });

    test('decrease limit', function() {
      // Decrease limit
      limited.$.repeater.__limit = 5;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 5);
      checkItemOrder(stamped);
      // Decrease limit
      limited.$.repeater.__limit = 0;
      limited.$.repeater.render();
      stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 0);
    });

    test('negative limit', function() {
      limited.$.repeater.__limit = -10;
      limited.$.repeater.render();
      var stamped = limited.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 0);
    });

  });

});

suite('chunked rendering', function() {

  let chunked;
  let notifyChange;
  let targetCount;
  const handleChange = () => {
    if (!targetCount || chunked.$.repeater.renderedItemCount >= targetCount) {
      notifyChange(Array.from(chunked.root.querySelectorAll('*:not(template):not(dom-repeat)')));
    }
  };
  const waitUntilRendered = async (count) => {
    targetCount = count;
    return await new Promise(r => notifyChange = r);
  };
  const expectNoChanges = async () => {
    targetCount = null;
    notifyChange = () => {
      assert.fail('no changes were expected');
    };
    // Ensure no changes happen in the next rAF+sT
    await new Promise(r => requestAnimationFrame(() => setTimeout(() => r())));
  };
  setup(() => {
    chunked = document.createElement('x-repeat-chunked');
    chunked.addEventListener('dom-change', handleChange);
    document.body.appendChild(chunked);
  });
  teardown(() => {
    chunked.removeEventListener('dom-change', handleChange);
    document.body.removeChild(chunked);
    chunked = null;
  });

  // Framerate=25, element cost = 4ms: should never make more than
  // (1000/25) / 4 = 10 elements per frame
  const MAX_PER_FRAME = (1000 / 25) / 4;

  test('basic chunked rendering', async () => {

    var checkItemOrder = function(stamped) {
      for (var i=0; i<stamped.length; i++) {
        assert.equal(stamped[i].textContent, i);
      }
    };

    // Set items to chunk
    chunked.items = chunked.preppedItems.slice();

    let stamped = [];
    let lastStamped;
    let frameCount = 0;
    // Loop until chunking is complete
    while (stamped.length < chunked.items.length) {
      frameCount++;
      stamped = await waitUntilRendered();
      checkItemOrder(stamped);
      if (!lastStamped) {
        // Initial rendering of initial count
        assert.equal(stamped.length, 10);
      } else {
        // Remaining rendering increments
        assert.isTrue(stamped.length > lastStamped.length,
          'list instance count should increase each frame');
        assert.deepEqual(lastStamped, stamped.slice(0, lastStamped.length),
          'list should not re-render instances during mutation');
        assert.isAtMost((stamped.length - lastStamped.length), MAX_PER_FRAME,
          `list should not render more than ${MAX_PER_FRAME} per frame`);
      }
      lastStamped = stamped;
    }
    // Final rendering at exact item count
    assert.equal(stamped.length, 100, 'final count wrong');
    assert.isAtLeast(frameCount, 10, 'should have taken at least 10 frames to render');
    
    // Set less than initial count, should trim list in one frame
    chunked.items = chunked.preppedItems.slice(0, 5);
    stamped = await waitUntilRendered();
    assert.equal(stamped.length, 5, 'final count wrong');
    assert.deepEqual(lastStamped.slice(0, stamped.length), stamped,
      'list should not re-render instances during mutation');
    lastStamped = stamped;

    // Set at initial count, should render in one frame
    chunked.items = chunked.preppedItems.slice(0, 10);
    stamped = await waitUntilRendered();
    assert.equal(stamped.length, 10, 'final count wrong');
    assert.deepEqual(lastStamped, stamped.slice(0, lastStamped.length),
      'list should not re-render instances during mutation');
    lastStamped = stamped;

    // Set over initial count, should render in more than one frame
    chunked.items = chunked.preppedItems.slice(0, 10 + MAX_PER_FRAME * 2);
    stamped = await waitUntilRendered();
    assert.deepEqual(lastStamped, stamped.slice(0, 10),
      'list should not re-render instances during mutation');
    frameCount = 0;
    while (stamped.length < chunked.items.length) {
      stamped = await waitUntilRendered();
      frameCount++;
    }
    assert.isAtLeast(frameCount, 2, 'should have taken at least 2 frames to render');
    assert.equal(stamped.length, chunked.items.length, 'final count wrong');

  });

  test('mutations during chunked rendering', async () => {

    var checkItemOrder = function(stamped) {
      var last = -1;
      for (var i=0; i<stamped.length; i++) {
        var curr = parseFloat(stamped[i].textContent);
        assert.isTrue(curr > last);
        last = curr;
      }
    };

    var mutateArray = function(repeater, renderedCount) {
      // The goal here is to remove & add some, and do it over
      // the threshold of where we have currently rendered items, and
      // ensure that the prop values of the newly inserted items are in
      // ascending order so we can do a simple check in checkItemOrder
      var overlap = 2;
      var remove = 4;
      var add = 6;
      var start = renderedCount.length - overlap;
      if (start + add < repeater.items.length) {
        var end = start + remove;
        var args = ['items', start, remove];
        var startVal = repeater.items[start].prop;
        var endVal = repeater.items[end].prop;
        var delta = (endVal - startVal) / add;
        for (var i=0; i<add; i++) {
          args.push({prop: startVal + i*delta});
        }
        repeater.splice.apply(repeater, args);
      }
    };

    // Set items to chunk
    chunked.items = chunked.preppedItems.slice();

    let stamped = [];
    let lastStamped;
    let frameCount = 0;
    // Loop until chunking is complete
    while (stamped.length < chunked.items.length) {
      stamped = await waitUntilRendered();
      checkItemOrder(stamped);
      if (!lastStamped) {
        // Initial rendering of initial count
        assert.equal(stamped.length, 10);
      } else {
        // Remaining rendering increments
        assert.isTrue(stamped.length > lastStamped.length,
          'list instance count should increase each frame');
        assert.deepEqual(lastStamped, stamped.slice(0, lastStamped.length),
          'list should not re-render instances during mutation');
        assert.isAtMost((stamped.length - lastStamped.length), MAX_PER_FRAME,
          `list should not render more than ${MAX_PER_FRAME} per frame`);
      }
      if (stamped.length < chunked.items.length && frameCount < 5) {
        mutateArray(chunked, stamped);
      }
      lastStamped = stamped;
      frameCount++;
    }
    // Final rendering at exact item count
    assert.equal(stamped.length, chunked.items.length, 'final count wrong');
    assert.isAtLeast(frameCount, 10, 'should have taken at least 10 frames to render');

  });


  test('mutations during chunked rendering, sort & filtered', async () => {

    var checkItemOrder = function(stamped) {
      var last = Infinity;
      for (var i=0; i<stamped.length; i++) {
        var curr = parseFloat(stamped[i].textContent);
        assert.isTrue(curr <= last);
        assert.strictEqual(curr % 2, 0);
        last = curr;
      }
    };

    var mutateArray = function(repeater, stamped) {
      var start = parseInt(stamped[0].textContent);
      var end = parseInt(stamped[stamped.length-1].textContent);
      var mid = (end-start)/2;
      for (var i=0; i<5; i++) {
        chunked.push('items', {prop: mid + 1});
      }
      chunked.splice('items', Math.round(stamped.length/2), 3);
    };

    // Set items to chunk
    chunked.$.repeater.sort = function(a, b) {
      return b.prop - a.prop;
    };
    chunked.$.repeater.filter = function(a) {
      return (a.prop % 2) === 0;
    };
    chunked.items = chunked.preppedItems.slice();

    let stamped = [];
    let lastStamped;
    let frameCount = 0;
    let filteredLength = chunked.items.filter(chunked.$.repeater.filter).length;
    // Loop until chunking is complete
    while (stamped.length < filteredLength) {
      stamped = await waitUntilRendered();
      checkItemOrder(stamped);
      if (!lastStamped) {
        // Initial rendering of initial count
        assert.equal(stamped.length, 10);
      } else {
        // Remaining rendering increments
        assert.isTrue(stamped.length > lastStamped.length,
          'list instance count should increase each frame');
        assert.deepEqual(lastStamped, stamped.slice(0, lastStamped.length),
          'list should not re-render instances during mutation');
        assert.isAtMost((stamped.length - lastStamped.length), MAX_PER_FRAME,
          `list should not render more than ${MAX_PER_FRAME} per frame`);
      }
      if (stamped.length < chunked.items.length && frameCount < 4) {
        mutateArray(chunked, stamped);
        filteredLength = chunked.items.filter(chunked.$.repeater.filter).length;
      }
      lastStamped = stamped;
      frameCount++;
    }
    // Final rendering at exact item count
    assert.equal(stamped.length, filteredLength, 'final count wrong');
    assert.isAtLeast(frameCount, 5, 'should have taken at least 5 frames to render');
  });

  suite('resetting items array', () => {

    [false, true].forEach(reuse => {

      test(`reuseChunkedInstances=${reuse}`, async () => {

        // Set items to chunk
        chunked.$.repeater.reuseChunkedInstances = reuse;
        chunked.items = chunked.preppedItems.slice();

        let resetCount = 3;
        let stamped = [];
        let lastStamped;
        let frameCount = 0;
        // Loop until chunking is complete
        while (stamped.length < chunked.items.length) {
          stamped = await waitUntilRendered();
          if (!lastStamped) {
            // Initial rendering of initial count
            assert.equal(stamped.length, 10);
          } else {
            // Remaining rendering increments
            assert.isTrue(stamped.length > lastStamped.length,
              'list instance count should increase each frame');
            assert.deepEqual(lastStamped, stamped.slice(0, lastStamped.length),
              'list should not re-render instances during mutation');
            assert.isAtMost((stamped.length - lastStamped.length), MAX_PER_FRAME,
              `list should not render more than ${MAX_PER_FRAME} per frame`);
          }
          lastStamped = stamped;
          frameCount++;
          if (--resetCount > 0) {
            if (!reuse) {
              lastStamped = null;
            }
            chunked.items = chunked.preppedItems.slice();
          }
        }
        // Final rendering at exact item count
        assert.equal(stamped.length, chunked.items.length, 'final count wrong');
        assert.isAtLeast(frameCount, 5, 'should have taken at least 5 frames to render');
        
      });

    });

  });

  test('changing to/from initialCount=0', async () => {
    // Render all
    chunked.items = chunked.preppedItems.slice();
    let stamped = await waitUntilRendered(chunked.preppedItems.length);
    assert.equal(stamped.length, chunked.preppedItems.length);
    // Clear the list
    chunked.items = [];
    stamped = await waitUntilRendered(0);
    assert.equal(stamped.length, 0);
    // Disable chunking
    chunked.$.repeater.initialCount = 0;
    // Render all
    chunked.items = chunked.preppedItems.slice();
    stamped = await waitUntilRendered(chunked.preppedItems.length);
    assert.equal(stamped.length, chunked.preppedItems.length);
    // Clear the list
    chunked.items = [];
    stamped = await waitUntilRendered(0);
    assert.equal(stamped.length, 0);
    // Re-enable chunking
    chunked.$.repeater.initialCount = 10;
    // Render all; the initial render should have the initial count, and then
    // chunk until the end of the list
    let frameCount = 0;
    chunked.items = chunked.preppedItems.slice();
    while (stamped.length < chunked.items.length) {
      stamped = await waitUntilRendered();
      if (frameCount === 0) {
        assert.equal(stamped.length, 10);
      }
      frameCount++;
    }
    assert.equal(stamped.length, chunked.preppedItems.length);
    assert.isAtLeast(frameCount, 2);
    // Disable chunking
    chunked.$.repeater.initialCount = 0;
    // Render some
    chunked.items = chunked.preppedItems.slice(0, 20);
    stamped = await waitUntilRendered();
    assert.equal(stamped.length, chunked.items.length);
    // Re-enable chunking
    chunked.$.repeater.initialCount = 10;
    // Push remaining; these should chunk out
    frameCount = 0;
    chunked.push('items', ...chunked.preppedItems.slice(20));
    while (stamped.length < chunked.items.length) {
      stamped = await waitUntilRendered();
      frameCount++;
    }
    assert.equal(stamped.length, chunked.items.length);
    assert.isAtLeast(frameCount, 2);
  });

  test('chunking stops after detaching, restarts after attaching', async () => {
    chunked.items = chunked.preppedItems.slice();
    const initialLength = (await waitUntilRendered()).length;
    assert.isAbove(initialLength, 0);
    assert.isBelow(initialLength, chunked.preppedItems.length);
    document.body.removeChild(chunked);
    await expectNoChanges();
    const done = waitUntilRendered(chunked.preppedItems.length);
    document.body.appendChild(chunked);
    const endLength = (await done).length;
    assert.equal(endLength, chunked.items.length);
  });

});

suite('misc', function() {

  test('large splice', function(done) {
    let primitiveLarge = fixture('primitiveLarge');
    flush();
    primitiveLarge.splice('items', 0, 10);
    setTimeout(function() {
      var stamped =
        primitiveLarge.root.querySelectorAll('*:not(template):not(dom-repeat)');
      assert.equal(stamped.length, 1, 'total stamped count incorrect');
      assert.equal(stamped[0].textContent, '10');
      done();
    });
  });

  test('css scoping retained when re-ordering', function() {
    if (!window.ShadyDOM) {
      this.skip();
    }
    let simple = fixture('simple');
    flush();
    var removed;
    // Confirm initial scoping
    var stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert(stamped[0].classList.contains('x-simple-repeat'), 'expected scoping');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    // Move
    removed = simple.splice('items', 0, 1);
    simple.splice('items', 1, 0, removed[0]);
    flush();
    stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    let movedItem = simple.$.repeat.modelForElement(stamped[1]).item;
    assert.equal(removed[0], movedItem);
    assert.equal(stamped[1].itemaProp, 'prop-1');
    assert(stamped[1].classList.contains('x-simple-repeat'), 'expected scoping');
    // Revert
    removed = simple.splice('items', 1, 1);
    simple.splice('items', 0, 0, removed[0]);
    flush();
    stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    movedItem = simple.$.repeat.modelForElement(stamped[0]).item;
    assert.equal(removed[0], movedItem);
    assert.equal(stamped[0].itemaProp, 'prop-1');
    assert(stamped[0].classList.contains('x-simple-repeat'), 'expected scoping');
  });

  test('paths update on observed properties', function() {
    let simple = fixture('simple');
    flush();
    var stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
    assert.equal(stamped[0].itemaProp, 'prop-1');
    simple.$.repeat.observe = 'prop';
    flush();
    simple.set('items.0.prop', {foo: 0});
    flush();
    assert.equal(stamped[0].get('itemaProp.foo'), 0);
    simple.set('items.0.prop.foo', 1);
    flush();
    assert.equal(stamped[0].get('itemaProp.foo'), 1);
    simple.set('items.0.prop.foo', 2);
    flush();
    assert.equal(stamped[0].get('itemaProp.foo'), 2);
  });
});

suite('timing', function() {

  test('non-upgrade case finds template', function() {
    assert.equal(nonUpgrade.textContent.trim(), 'stamped');
  });

});

suite('dom-change composed', function() {

  test('dom-change event composed, bubbles outside dom-if scope', function() {
    var el = fixture('simple');
    var domChangeFired = 0;
    el.addEventListener('dom-change', function() {
      domChangeFired++;
    });
    el.push('items', {prop: 'added1'});
    flush();
    el.push('items', {prop: 'added2'});
    flush();
    assert.equal(domChangeFired, 2);
  });
});
</script>

</body>
</html>
